From c7d9ca23f1495e1d8830a98af2079a56b17c8e6f Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 25 Mar 2025 23:53:06 +0000 Subject: [PATCH 1/5] Merge event counters and performance counters Move functionality into a new Microsoft.Data.SqlClient.Diagnostics.SqlClientMetrics type. This common interface encapsulates both event counters and performance counters. --- .../src/Microsoft.Data.SqlClient.csproj | 7 +- .../SqlClientEventSource.NetCoreApp.cs | 396 ------------------ .../netfx/src/Microsoft.Data.SqlClient.csproj | 9 +- .../Data/SqlClient/SqlConnectionFactory.cs | 16 +- .../Data/ProviderBase/DbConnectionFactory.cs | 88 +--- .../Data/ProviderBase/DbConnectionInternal.cs | 70 +--- .../ConnectionPool/DbConnectionPool.cs | 3 - .../DbConnectionPoolCounters.netfx.cs | 361 ---------------- .../ConnectionPool/DbConnectionPoolGroup.cs | 14 +- .../WaitHandleDbConnectionPool.cs | 111 ++--- .../SqlClient/Diagnostics/SqlClientMetrics.cs | 346 +++++++++++++++ .../Diagnostics/SqlClientMetrics.netcore.cs | 178 ++++++++ .../Diagnostics/SqlClientMetrics.netfx.cs | 193 +++++++++ .../Data/SqlClient/SqlClientEventSource.cs | 77 +--- .../TracingTests/EventCounterTest.cs | 39 +- 15 files changed, 824 insertions(+), 1084 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientEventSource.NetCoreApp.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolCounters.netfx.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index bb77ffba3e..226d103078 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -209,6 +209,12 @@ Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenError.netcore.cs + + Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.netcore.cs + Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitAfter.netcore.cs @@ -716,7 +722,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientEventSource.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientEventSource.NetCoreApp.cs deleted file mode 100644 index c0312ca219..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientEventSource.NetCoreApp.cs +++ /dev/null @@ -1,396 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics.Tracing; -using System.Threading; - -namespace Microsoft.Data.SqlClient -{ - /// - /// supported frameworks: .Net core 3.1 and .Net standard 2.1 and above - /// - internal partial class SqlClientEventSource : SqlClientEventSourceBase - { - private PollingCounter _activeHardConnections; - private IncrementingPollingCounter _hardConnectsPerSecond; - private IncrementingPollingCounter _hardDisconnectsPerSecond; - - private PollingCounter _activeSoftConnections; - private IncrementingPollingCounter _softConnects; - private IncrementingPollingCounter _softDisconnects; - - private PollingCounter _numberOfNonPooledConnections; - private PollingCounter _numberOfPooledConnections; - - private PollingCounter _numberOfActiveConnectionPoolGroups; - private PollingCounter _numberOfInactiveConnectionPoolGroups; - - private PollingCounter _numberOfActiveConnectionPools; - private PollingCounter _numberOfInactiveConnectionPools; - - private PollingCounter _numberOfActiveConnections; - private PollingCounter _numberOfFreeConnections; - private PollingCounter _numberOfStasisConnections; - private IncrementingPollingCounter _numberOfReclaimedConnections; - - private long _activeHardConnectionsCounter = 0; - private long _hardConnectsCounter = 0; - private long _hardDisconnectsCounter = 0; - - private long _activeSoftConnectionsCounter = 0; - private long _softConnectsCounter = 0; - private long _softDisconnectsCounter = 0; - - private long _nonPooledConnectionsCounter = 0; - private long _pooledConnectionsCounter = 0; - - private long _activeConnectionPoolGroupsCounter = 0; - private long _inactiveConnectionPoolGroupsCounter = 0; - - private long _activeConnectionPoolsCounter = 0; - private long _inactiveConnectionPoolsCounter = 0; - - private long _activeConnectionsCounter = 0; - private long _freeConnectionsCounter = 0; - private long _stasisConnectionsCounter = 0; - private long _reclaimedConnectionsCounter = 0; - - protected override void EventCommandMethodCall(EventCommandEventArgs command) - { - if(command.Command != EventCommand.Enable) - { - return; - } - - _activeHardConnections = _activeHardConnections ?? - new PollingCounter("active-hard-connections", this, () => _activeHardConnectionsCounter) - { - DisplayName = "Actual active connections currently made to servers", - DisplayUnits = "count" - }; - - _hardConnectsPerSecond = _hardConnectsPerSecond ?? - new IncrementingPollingCounter("hard-connects", this, () => _hardConnectsCounter) - { - DisplayName = "Actual connection rate to servers", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _hardDisconnectsPerSecond = _hardDisconnectsPerSecond ?? - new IncrementingPollingCounter("hard-disconnects", this, () => _hardDisconnectsCounter) - { - DisplayName = "Actual disconnection rate from servers", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _activeSoftConnections = _activeSoftConnections ?? - new PollingCounter("active-soft-connects", this, () => _activeSoftConnectionsCounter) - { - DisplayName = "Active connections retrieved from the connection pool", - DisplayUnits = "count" - }; - - _softConnects = _softConnects ?? - new IncrementingPollingCounter("soft-connects", this, () => _softConnectsCounter) - { - DisplayName = "Rate of connections retrieved from the connection pool", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _softDisconnects = _softDisconnects ?? - new IncrementingPollingCounter("soft-disconnects", this, () => _softDisconnectsCounter) - { - DisplayName = "Rate of connections returned to the connection pool", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _numberOfNonPooledConnections = _numberOfNonPooledConnections ?? - new PollingCounter("number-of-non-pooled-connections", this, () => _nonPooledConnectionsCounter) - { - DisplayName = "Number of connections not using connection pooling", - DisplayUnits = "count" - }; - - _numberOfPooledConnections = _numberOfPooledConnections ?? - new PollingCounter("number-of-pooled-connections", this, () => _pooledConnectionsCounter) - { - DisplayName = "Number of connections managed by the connection pool", - DisplayUnits = "count" - }; - - _numberOfActiveConnectionPoolGroups = _numberOfActiveConnectionPoolGroups ?? - new PollingCounter("number-of-active-connection-pool-groups", this, () => _activeConnectionPoolGroupsCounter) - { - DisplayName = "Number of active unique connection strings", - DisplayUnits = "count" - }; - - _numberOfInactiveConnectionPoolGroups = _numberOfInactiveConnectionPoolGroups ?? - new PollingCounter("number-of-inactive-connection-pool-groups", this, () => _inactiveConnectionPoolGroupsCounter) - { - DisplayName = "Number of unique connection strings waiting for pruning", - DisplayUnits = "count" - }; - - _numberOfActiveConnectionPools = _numberOfActiveConnectionPools ?? - new PollingCounter("number-of-active-connection-pools", this, () => _activeConnectionPoolsCounter) - { - DisplayName = "Number of active connection pools", - DisplayUnits = "count" - }; - - _numberOfInactiveConnectionPools = _numberOfInactiveConnectionPools ?? - new PollingCounter("number-of-inactive-connection-pools", this, () => _inactiveConnectionPoolsCounter) - { - DisplayName = "Number of inactive connection pools", - DisplayUnits = "count" - }; - - _numberOfActiveConnections = _numberOfActiveConnections ?? - new PollingCounter("number-of-active-connections", this, () => _activeConnectionsCounter) - { - DisplayName = "Number of active connections", - DisplayUnits = "count" - }; - - _numberOfFreeConnections = _numberOfFreeConnections ?? - new PollingCounter("number-of-free-connections", this, () => _freeConnectionsCounter) - { - DisplayName = "Number of ready connections in the connection pool", - DisplayUnits = "count" - }; - - _numberOfStasisConnections = _numberOfStasisConnections ?? - new PollingCounter("number-of-stasis-connections", this, () => _stasisConnectionsCounter) - { - DisplayName = "Number of connections currently waiting to be ready", - DisplayUnits = "count" - }; - - _numberOfReclaimedConnections = _numberOfReclaimedConnections ?? - new IncrementingPollingCounter("number-of-reclaimed-connections", this, () => _reclaimedConnectionsCounter) - { - DisplayName = "Number of reclaimed connections from GC", - DisplayUnits = "count", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - } - - /// - /// The number of actual connections that are being made to servers - /// - [NonEvent] - internal override void HardConnectRequest() - { - Interlocked.Increment(ref _activeHardConnectionsCounter); - Interlocked.Increment(ref _hardConnectsCounter); - } - - /// - /// The number of actual disconnects that are being made to servers - /// - [NonEvent] - internal override void HardDisconnectRequest() - { - Interlocked.Decrement(ref _activeHardConnectionsCounter); - Interlocked.Increment(ref _hardDisconnectsCounter); - } - - /// - /// The number of connections we get from the pool - /// - [NonEvent] - internal override void SoftConnectRequest() - { - Interlocked.Increment(ref _activeSoftConnectionsCounter); - Interlocked.Increment(ref _softConnectsCounter); - } - - /// - /// The number of connections we return to the pool - /// - [NonEvent] - internal override void SoftDisconnectRequest() - { - Interlocked.Decrement(ref _activeSoftConnectionsCounter); - Interlocked.Increment(ref _softDisconnectsCounter); - } - - /// - /// The number of connections that are not using connection pooling - /// - [NonEvent] - internal override void EnterNonPooledConnection() - { - Interlocked.Increment(ref _nonPooledConnectionsCounter); - } - - /// - /// The number of connections that are not using connection pooling - /// - [NonEvent] - internal override void ExitNonPooledConnection() - { - Interlocked.Decrement(ref _nonPooledConnectionsCounter); - } - - /// - /// The number of connections that are managed by the connection pool - /// - [NonEvent] - internal override void EnterPooledConnection() - { - Interlocked.Increment(ref _pooledConnectionsCounter); - } - - /// - /// The number of connections that are managed by the connection pool - /// - [NonEvent] - internal override void ExitPooledConnection() - { - Interlocked.Decrement(ref _pooledConnectionsCounter); - } - - /// - /// The number of unique connection strings - /// - [NonEvent] - internal override void EnterActiveConnectionPoolGroup() - { - Interlocked.Increment(ref _activeConnectionPoolGroupsCounter); - } - - /// - /// The number of unique connection strings - /// - [NonEvent] - internal override void ExitActiveConnectionPoolGroup() - { - Interlocked.Decrement(ref _activeConnectionPoolGroupsCounter); - } - - /// - /// The number of unique connection strings waiting for pruning - /// - [NonEvent] - internal override void EnterInactiveConnectionPoolGroup() - { - Interlocked.Increment(ref _inactiveConnectionPoolGroupsCounter); - } - - /// - /// The number of unique connection strings waiting for pruning - /// - [NonEvent] - internal override void ExitInactiveConnectionPoolGroup() - { - Interlocked.Decrement(ref _inactiveConnectionPoolGroupsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void EnterActiveConnectionPool() - { - Interlocked.Increment(ref _activeConnectionPoolsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void ExitActiveConnectionPool() - { - Interlocked.Decrement(ref _activeConnectionPoolsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void EnterInactiveConnectionPool() - { - Interlocked.Increment(ref _inactiveConnectionPoolsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void ExitInactiveConnectionPool() - { - Interlocked.Decrement(ref _inactiveConnectionPoolsCounter); - } - - /// - /// The number of connections currently in-use - /// - [NonEvent] - internal override void EnterActiveConnection() - { - Interlocked.Increment(ref _activeConnectionsCounter); - } - - /// - /// The number of connections currently in-use - /// - [NonEvent] - internal override void ExitActiveConnection() - { - Interlocked.Decrement(ref _activeConnectionsCounter); - } - - /// - /// The number of connections currently available for use - /// - [NonEvent] - internal override void EnterFreeConnection() - { - Interlocked.Increment(ref _freeConnectionsCounter); - } - - /// - /// The number of connections currently available for use - /// - [NonEvent] - internal override void ExitFreeConnection() - { - Interlocked.Decrement(ref _freeConnectionsCounter); - } - - /// - /// The number of connections currently waiting to be made ready for use - /// - [NonEvent] - internal override void EnterStasisConnection() - { - Interlocked.Increment(ref _stasisConnectionsCounter); - } - - /// - /// The number of connections currently waiting to be made ready for use - /// - [NonEvent] - internal override void ExitStasisConnection() - { - Interlocked.Decrement(ref _stasisConnectionsCounter); - } - - /// - /// The number of connections we reclaim from GC'd external connections - /// - [NonEvent] - internal override void ReclaimedConnectionRequest() - { - Interlocked.Increment(ref _reclaimedConnectionsCounter); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index e5840b17a3..bd45398408 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -271,9 +271,6 @@ Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContextKey.cs - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolCounters.netfx.cs - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroup.cs @@ -310,6 +307,12 @@ Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.netfx.cs + Microsoft\Data\ProviderBase\DbMetaDataFactory.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index d9bf71b663..d304161b58 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -18,7 +18,7 @@ namespace Microsoft.Data.SqlClient { sealed internal class SqlConnectionFactory : DbConnectionFactory { - private SqlConnectionFactory() : base(SqlPerformanceCounters.SingletonInstance) + private SqlConnectionFactory() : base() { } @@ -343,19 +343,5 @@ override internal void SetInnerConnectionTo(DbConnection owningObject, DbConnect } } - - [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")] - sealed internal class SqlPerformanceCounters : DbConnectionPoolCounters - { - private const string CategoryName = ".NET Data Provider for SqlServer"; - private const string CategoryHelp = "Counters for Microsoft.Data.SqlClient"; - - public static readonly SqlPerformanceCounters SingletonInstance = new SqlPerformanceCounters(); - - [System.Diagnostics.PerformanceCounterPermissionAttribute(System.Security.Permissions.SecurityAction.Assert, PermissionAccess = PerformanceCounterPermissionAccess.Write, MachineName = ".", CategoryName = CategoryName)] - private SqlPerformanceCounters() : base(CategoryName, CategoryHelp) - { - } - } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs index f357e6109b..61c4e18579 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs @@ -34,25 +34,6 @@ internal abstract class DbConnectionFactory private static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount]; private static Task s_completedTask; -#if NETFRAMEWORK - private readonly DbConnectionPoolCounters _performanceCounters; - - protected DbConnectionFactory() : this(DbConnectionPoolCountersNoCounters.SingletonInstance) { } - - protected DbConnectionFactory(DbConnectionPoolCounters performanceCounters) - { - _performanceCounters = performanceCounters; - _connectionPoolGroups = new Dictionary(); - _poolsToRelease = new List(); - _poolGroupsToRelease = new List(); - _pruningTimer = CreatePruningTimer(); - } - - internal DbConnectionPoolCounters PerformanceCounters - { - get { return _performanceCounters; } - } -#else protected DbConnectionFactory() { _connectionPoolGroups = new Dictionary(); @@ -60,7 +41,6 @@ protected DbConnectionFactory() _poolGroupsToRelease = new List(); _pruningTimer = CreatePruningTimer(); } -#endif public abstract DbProviderFactory ProviderFactory { @@ -135,13 +115,8 @@ internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConne DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions); if (newConnection != null) { -#if NETFRAMEWORK - PerformanceCounters.HardConnectsPerSecond.Increment(); - newConnection.MakeNonPooledObject(owningConnection, PerformanceCounters); -#else - SqlClientEventSource.Log.HardConnectRequest(); + SqlClientEventSource.Metrics.HardConnectRequest(); newConnection.MakeNonPooledObject(owningConnection); -#endif } SqlClientEventSource.Log.TryTraceEvent(" {0}, Non-pooled database connection created.", ObjectID); return newConnection; @@ -155,11 +130,8 @@ internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbCo if (newConnection != null) { -#if NETFRAMEWORK - PerformanceCounters.HardConnectsPerSecond.Increment(); -#else - SqlClientEventSource.Log.HardConnectRequest(); -#endif + SqlClientEventSource.Metrics.HardConnectRequest(); + newConnection.MakePooledConnection(pool); } SqlClientEventSource.Log.TryTraceEvent(" {0}, Pooled database connection created.", ObjectID); @@ -289,11 +261,8 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour } connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); -#if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Increment(); -#else - SqlClientEventSource.Log.EnterNonPooledConnection(); -#endif + + SqlClientEventSource.Metrics.EnterNonPooledConnection(); } else { @@ -397,11 +366,7 @@ private void TryGetConnectionCompletedContinuation(Task ta } else { -#if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Increment(); -#else - SqlClientEventSource.Log.EnterNonPooledConnection(); -#endif + SqlClientEventSource.Metrics.EnterNonPooledConnection(); } } } @@ -510,11 +475,8 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, D // lock prevents race condition with PruneConnectionPoolGroups newConnectionPoolGroups.Add(key, newConnectionPoolGroup); -#if NETFRAMEWORK - PerformanceCounters.NumberOfActiveConnectionPoolGroups.Increment(); -#else - SqlClientEventSource.Log.EnterActiveConnectionPoolGroup(); -#endif + + SqlClientEventSource.Metrics.EnterActiveConnectionPoolGroup(); connectionPoolGroup = newConnectionPoolGroup; _connectionPoolGroups = newConnectionPoolGroups; } @@ -579,11 +541,8 @@ private void PruneConnectionPoolGroups(object state) { _poolsToRelease.Remove(pool); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.ObjectId); -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPools.Decrement(); -#else - SqlClientEventSource.Log.ExitInactiveConnectionPool(); -#endif + + SqlClientEventSource.Metrics.ExitInactiveConnectionPool(); } } } @@ -608,11 +567,8 @@ private void PruneConnectionPoolGroups(object state) { _poolGroupsToRelease.Remove(poolGroup); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePoolGroup={1}", ObjectID, poolGroup.ObjectID); -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Decrement(); -#else - SqlClientEventSource.Log.ExitInactiveConnectionPoolGroup(); -#endif + + SqlClientEventSource.Metrics.ExitInactiveConnectionPoolGroup(); } } } @@ -639,9 +595,6 @@ private void PruneConnectionPoolGroups(object state) if (entry.Value.Prune()) { // may add entries to _poolsToRelease -#if NETFRAMEWORK - PerformanceCounters.NumberOfActiveConnectionPoolGroups.Decrement(); -#endif QueuePoolGroupForRelease(entry.Value); } else @@ -674,12 +627,8 @@ internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) } _poolsToRelease.Add(pool); } -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPools.Increment(); -#else - SqlClientEventSource.Log.EnterInactiveConnectionPool(); - SqlClientEventSource.Log.ExitActiveConnectionPool(); -#endif + SqlClientEventSource.Metrics.EnterInactiveConnectionPool(); + SqlClientEventSource.Metrics.ExitActiveConnectionPool(); } internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) @@ -691,12 +640,9 @@ internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) { _poolGroupsToRelease.Add(poolGroup); } -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Increment(); -#else - SqlClientEventSource.Log.EnterInactiveConnectionPoolGroup(); - SqlClientEventSource.Log.ExitActiveConnectionPoolGroup(); -#endif + + SqlClientEventSource.Metrics.EnterInactiveConnectionPoolGroup(); + SqlClientEventSource.Metrics.ExitActiveConnectionPoolGroup(); } virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs index 7268f00eea..b2cf6c25af 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -324,10 +324,6 @@ protected internal DbConnection Owner get => _owningObject.TryGetTarget(out DbConnection connection) ? connection : null; } - #if NETFRAMEWORK - protected DbConnectionPoolCounters PerformanceCounters { get; private set; } - #endif - protected virtual bool ReadyToPrepareTransaction { get => true; @@ -368,11 +364,7 @@ internal void ActivateConnection(Transaction transaction) Activate(transaction); - #if NETFRAMEWORK - PerformanceCounters.NumberOfActiveConnections.Increment(); - #else - SqlClientEventSource.Log.EnterActiveConnection(); - #endif + SqlClientEventSource.Metrics.EnterActiveConnection(); } internal void AddWeakReference(object value, int tag) @@ -485,11 +477,7 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac // and transactions may not get cleaned up... Deactivate(); - #if NETFRAMEWORK - PerformanceCounters.HardDisconnectsPerSecond.Increment(); - #else - SqlClientEventSource.Log.HardDisconnectRequest(); - #endif + SqlClientEventSource.Metrics.HardDisconnectRequest(); // To prevent an endless recursion, we need to clear the owning object // before we call dispose so that we can't get here a second time... @@ -504,16 +492,8 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac } else { - #if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Decrement(); - if (this is not SqlInternalConnectionSmi) - { - Dispose(); - } - #else - SqlClientEventSource.Log.ExitNonPooledConnection(); + SqlClientEventSource.Metrics.ExitNonPooledConnection(); Dispose(); - #endif } } } @@ -543,15 +523,7 @@ internal void DeactivateConnection() Debug.Assert(activateCount == 0, "activated multiple times?"); #endif - #if NETFRAMEWORK - if (PerformanceCounters is not null) - { - // Pool.Clear will DestroyObject that will clean performanceCounters before going here - PerformanceCounters.NumberOfActiveConnections.Decrement(); - } - #else - SqlClientEventSource.Log.ExitActiveConnection(); - #endif + SqlClientEventSource.Metrics.ExitActiveConnection(); if (!IsConnectionDoomed && Pool.UseLoadBalancing) { @@ -611,11 +583,7 @@ internal virtual void DelegatedTransactionEnded() // once and for all, or the server will have fits about us // leaving connections open until the client-side GC kicks // in. - #if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Decrement(); - #else - SqlClientEventSource.Log.ExitNonPooledConnection(); - #endif + SqlClientEventSource.Metrics.ExitNonPooledConnection(); Dispose(); } @@ -690,10 +658,6 @@ public virtual void Dispose() IsConnectionDoomed = true; _enlistedTransactionOriginal = null; // should not be disposed - #if NETFRAMEWORK - PerformanceCounters = null; - #endif - // Dispose of the _enlistedTransaction since it is a clone of the original reference. // VSDD 780271 - _enlistedTransaction can be changed by another thread (TX end event) Transaction enlistedTransaction = Interlocked.Exchange(ref _enlistedTransaction, null); @@ -723,16 +687,8 @@ public virtual void Dispose() /// /// Used by DbConnectionFactory to indicate that this object IS NOT part of a connection pool. /// - #if NETFRAMEWORK - internal void MakeNonPooledObject(DbConnection owningObject, DbConnectionPoolCounters performanceCounters) - #else internal void MakeNonPooledObject(DbConnection owningObject) - #endif { - #if NETFRAMEWORK - PerformanceCounters = performanceCounters; - #endif - Pool = null; _owningObject.SetTarget(owningObject); _pooledCount = -1; @@ -746,10 +702,6 @@ internal void MakePooledConnection(DbConnectionPool connectionPool) { _createTime = DateTime.UtcNow; Pool = connectionPool; - - #if NETFRAMEWORK - PerformanceCounters = connectionPool.PerformanceCounters; - #endif } internal void NotifyWeakReference(int message) => @@ -849,11 +801,7 @@ internal void SetInStasis() IsTxRootWaitingForTxEnd = true; SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Non-Pooled Connection has Delegated Transaction, waiting to Dispose.", ObjectID); - #if NETFRAMEWORK - PerformanceCounters.NumberOfStasisConnections.Increment(); - #else - SqlClientEventSource.Log.EnterStasisConnection(); - #endif + SqlClientEventSource.Metrics.EnterStasisConnection(); } /// @@ -1005,11 +953,7 @@ private void TerminateStasis(bool returningToPool) : "Delegated Transaction has ended, connection is closed/leaked. Disposing."; SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, {1}", ObjectID, message); - #if NETFRAMEWORK - PerformanceCounters.NumberOfStasisConnections.Decrement(); - #else - SqlClientEventSource.Log.ExitStasisConnection(); - #endif + SqlClientEventSource.Metrics.ExitStasisConnection(); IsTxRootWaitingForTxEnd = false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs index a4efd25d2c..0d00227469 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPool.cs @@ -33,9 +33,6 @@ internal abstract class DbConnectionPool internal abstract bool IsRunning { get; } -#if NETFRAMEWORK - internal abstract DbConnectionPoolCounters PerformanceCounters { get; } -#endif internal abstract DbConnectionPoolGroup PoolGroup { get; } internal abstract DbConnectionPoolGroupOptions PoolGroupOptions { get; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolCounters.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolCounters.netfx.cs deleted file mode 100644 index 0eadb7f42c..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolCounters.netfx.cs +++ /dev/null @@ -1,361 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Interop.Windows.Kernel32; - -#if NETFRAMEWORK - -namespace Microsoft.Data.SqlClient.ConnectionPool -{ - - using System; - using System.Diagnostics; - using System.Reflection; - using System.Runtime.ConstrainedExecution; - using System.Runtime.Versioning; - using System.Security.Permissions; - using Microsoft.Data.Common; - - internal abstract class DbConnectionPoolCounters - { - private static class CreationData - { - - static internal readonly CounterCreationData HardConnectsPerSecond = new CounterCreationData( - "HardConnectsPerSecond", - "The number of actual connections per second that are being made to servers", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData HardDisconnectsPerSecond = new CounterCreationData( - "HardDisconnectsPerSecond", - "The number of actual disconnects per second that are being made to servers", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData SoftConnectsPerSecond = new CounterCreationData( - "SoftConnectsPerSecond", - "The number of connections we get from the pool per second", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData SoftDisconnectsPerSecond = new CounterCreationData( - "SoftDisconnectsPerSecond", - "The number of connections we return to the pool per second", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData NumberOfNonPooledConnections = new CounterCreationData( - "NumberOfNonPooledConnections", - "The number of connections that are not using connection pooling", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfPooledConnections = new CounterCreationData( - "NumberOfPooledConnections", - "The number of connections that are managed by the connection pooler", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfActiveConnectionPoolGroups = new CounterCreationData( - "NumberOfActiveConnectionPoolGroups", - "The number of unique connection strings", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfInactiveConnectionPoolGroups = new CounterCreationData( - "NumberOfInactiveConnectionPoolGroups", - "The number of unique connection strings waiting for pruning", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfActiveConnectionPools = new CounterCreationData( - "NumberOfActiveConnectionPools", - "The number of connection pools", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfInactiveConnectionPools = new CounterCreationData( - "NumberOfInactiveConnectionPools", - "The number of connection pools", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfActiveConnections = new CounterCreationData( - "NumberOfActiveConnections", - "The number of connections currently in-use", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfFreeConnections = new CounterCreationData( - "NumberOfFreeConnections", - "The number of connections currently available for use", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfStasisConnections = new CounterCreationData( - "NumberOfStasisConnections", - "The number of connections currently waiting to be made ready for use", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfReclaimedConnections = new CounterCreationData( - "NumberOfReclaimedConnections", - "The number of connections we reclaim from GC'd external connections", - PerformanceCounterType.NumberOfItems32); - }; - - sealed internal class Counter - { - private PerformanceCounter _instance; - - internal Counter(string categoryName, string instanceName, string counterName, PerformanceCounterType counterType) - { - if (ADP.s_isPlatformNT5) - { - try - { - if (!ADP.IsEmpty(categoryName) && !ADP.IsEmpty(instanceName)) - { - PerformanceCounter instance = new PerformanceCounter(); - instance.CategoryName = categoryName; - instance.CounterName = counterName; - instance.InstanceName = instanceName; - instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process; - instance.ReadOnly = false; - instance.RawValue = 0; // make sure we start out at zero - _instance = instance; - } - } - catch (InvalidOperationException e) - { - ADP.TraceExceptionWithoutRethrow(e); - // TODO: generate Application EventLog entry about inability to find perf counter - } - } - } - - - internal void Decrement() - { - PerformanceCounter instance = _instance; - if (instance != null) - { - instance.Decrement(); - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - internal void Dispose() - { // TODO: race condition, Dispose at the same time as Increment/Decrement - PerformanceCounter instance = _instance; - _instance = null; - if (instance != null) - { - instance.RemoveInstance(); - // should we be calling instance.Close? - // if we do will it exacerbate the Dispose vs. Decrement race condition - //instance.Close(); - } - } - - internal void Increment() - { - PerformanceCounter instance = _instance; - if (instance != null) - { - instance.Increment(); - } - } - }; - - const int CounterInstanceNameMaxLength = 127; - - internal readonly Counter HardConnectsPerSecond; - internal readonly Counter HardDisconnectsPerSecond; - internal readonly Counter SoftConnectsPerSecond; - internal readonly Counter SoftDisconnectsPerSecond; - internal readonly Counter NumberOfNonPooledConnections; - internal readonly Counter NumberOfPooledConnections; - internal readonly Counter NumberOfActiveConnectionPoolGroups; - internal readonly Counter NumberOfInactiveConnectionPoolGroups; - internal readonly Counter NumberOfActiveConnectionPools; - internal readonly Counter NumberOfInactiveConnectionPools; - internal readonly Counter NumberOfActiveConnections; - internal readonly Counter NumberOfFreeConnections; - internal readonly Counter NumberOfStasisConnections; - internal readonly Counter NumberOfReclaimedConnections; - - protected DbConnectionPoolCounters() : this(null, null) - { - } - - protected DbConnectionPoolCounters(string categoryName, string categoryHelp) - { - AppDomain.CurrentDomain.DomainUnload += new EventHandler(this.UnloadEventHandler); - AppDomain.CurrentDomain.ProcessExit += new EventHandler(this.ExitEventHandler); - AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(this.ExceptionEventHandler); - - string instanceName = null; - - if (!ADP.IsEmpty(categoryName)) - { - if (ADP.s_isPlatformNT5) - { - instanceName = GetInstanceName(); - } - } - - // level 0-3: hard connects/disconnects, plus basic pool/pool entry statistics - string basicCategoryName = categoryName; - HardConnectsPerSecond = new Counter(basicCategoryName, instanceName, CreationData.HardConnectsPerSecond.CounterName, CreationData.HardConnectsPerSecond.CounterType); - HardDisconnectsPerSecond = new Counter(basicCategoryName, instanceName, CreationData.HardDisconnectsPerSecond.CounterName, CreationData.HardDisconnectsPerSecond.CounterType); - NumberOfNonPooledConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfNonPooledConnections.CounterName, CreationData.NumberOfNonPooledConnections.CounterType); - NumberOfPooledConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfPooledConnections.CounterName, CreationData.NumberOfPooledConnections.CounterType); - NumberOfActiveConnectionPoolGroups = new Counter(basicCategoryName, instanceName, CreationData.NumberOfActiveConnectionPoolGroups.CounterName, CreationData.NumberOfActiveConnectionPoolGroups.CounterType); - NumberOfInactiveConnectionPoolGroups = new Counter(basicCategoryName, instanceName, CreationData.NumberOfInactiveConnectionPoolGroups.CounterName, CreationData.NumberOfInactiveConnectionPoolGroups.CounterType); - NumberOfActiveConnectionPools = new Counter(basicCategoryName, instanceName, CreationData.NumberOfActiveConnectionPools.CounterName, CreationData.NumberOfActiveConnectionPools.CounterType); - NumberOfInactiveConnectionPools = new Counter(basicCategoryName, instanceName, CreationData.NumberOfInactiveConnectionPools.CounterName, CreationData.NumberOfInactiveConnectionPools.CounterType); - NumberOfStasisConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfStasisConnections.CounterName, CreationData.NumberOfStasisConnections.CounterType); - NumberOfReclaimedConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfReclaimedConnections.CounterName, CreationData.NumberOfReclaimedConnections.CounterType); - - // level 4: expensive stuff - string verboseCategoryName = null; - if (!ADP.IsEmpty(categoryName)) - { - // don't load TraceSwitch if no categoryName so that Odbc/OleDb have a chance of not loading TraceSwitch - // which are also used by System.Diagnostics.PerformanceCounter.ctor & System.Transactions.get_Current - TraceSwitch perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); - if (TraceLevel.Verbose == perfCtrSwitch.Level) - { - verboseCategoryName = categoryName; - } - } - SoftConnectsPerSecond = new Counter(verboseCategoryName, instanceName, CreationData.SoftConnectsPerSecond.CounterName, CreationData.SoftConnectsPerSecond.CounterType); - SoftDisconnectsPerSecond = new Counter(verboseCategoryName, instanceName, CreationData.SoftDisconnectsPerSecond.CounterName, CreationData.SoftDisconnectsPerSecond.CounterType); - NumberOfActiveConnections = new Counter(verboseCategoryName, instanceName, CreationData.NumberOfActiveConnections.CounterName, CreationData.NumberOfActiveConnections.CounterType); - NumberOfFreeConnections = new Counter(verboseCategoryName, instanceName, CreationData.NumberOfFreeConnections.CounterName, CreationData.NumberOfFreeConnections.CounterType); - } - - [FileIOPermission(SecurityAction.Assert, Unrestricted = true)] - private string GetAssemblyName() - { - string result = null; - - // First try GetEntryAssembly name, then AppDomain.FriendlyName. - Assembly assembly = Assembly.GetEntryAssembly(); - - if (assembly != null) - { - AssemblyName name = assembly.GetName(); - if (name != null) - { - result = name.Name; // MDAC 73469 - } - } - return result; - } - - // SxS: this method uses GetCurrentProcessId to construct the instance name. - // TODO: VSDD 534795 - remove the Resource* attributes if you do not use GetCurrentProcessId after the fix - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] - private string GetInstanceName() - { - string result = null; - - string instanceName = GetAssemblyName(); // instance perfcounter name - - if (ADP.IsEmpty(instanceName)) - { - AppDomain appDomain = AppDomain.CurrentDomain; - if (appDomain != null) - { - instanceName = appDomain.FriendlyName; - } - } - - // TODO: If you do not use GetCurrentProcessId after fixing VSDD 534795, please remove Resource* attributes from this method - int pid = Kernel32Safe.GetCurrentProcessId(); - - - // SQLBUDT #366157 -there are several characters which have special meaning - // to PERFMON. They recommend that we translate them as shown below, to - // prevent problems. - - result = String.Format((IFormatProvider)null, "{0}[{1}]", instanceName, pid); - result = result.Replace('(', '[').Replace(')', ']').Replace('#', '_').Replace('/', '_').Replace('\\', '_'); - - // SQLBUVSTS #94625 - counter instance name cannot be greater than 127 - if (result.Length > CounterInstanceNameMaxLength) - { - // Replacing the middle part with "[...]" - // For example: if path is c:\long_path\very_(Ax200)_long__path\perftest.exe and process ID is 1234 than the resulted instance name will be: - // c:\long_path\very_(AxM)[...](AxN)_long__path\perftest.exe[1234] - // while M and N are adjusted to make each part before and after the [...] = 61 (making the total = 61 + 5 + 61 = 127) - const string insertString = "[...]"; - int firstPartLength = (CounterInstanceNameMaxLength - insertString.Length) / 2; - int lastPartLength = CounterInstanceNameMaxLength - firstPartLength - insertString.Length; - result = string.Format((IFormatProvider)null, "{0}{1}{2}", - result.Substring(0, firstPartLength), - insertString, - result.Substring(result.Length - lastPartLength, lastPartLength)); - - Debug.Assert(result.Length == CounterInstanceNameMaxLength, - string.Format((IFormatProvider)null, "wrong calculation of the instance name: expected {0}, actual: {1}", CounterInstanceNameMaxLength, result.Length)); - } - - return result; - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - public void Dispose() - { - // ExceptionEventHandler with IsTerminating may be called before - // the Connection Close is called or the variables are initialized - SafeDispose(HardConnectsPerSecond); - SafeDispose(HardDisconnectsPerSecond); - SafeDispose(SoftConnectsPerSecond); - SafeDispose(SoftDisconnectsPerSecond); - SafeDispose(NumberOfNonPooledConnections); - SafeDispose(NumberOfPooledConnections); - SafeDispose(NumberOfActiveConnectionPoolGroups); - SafeDispose(NumberOfInactiveConnectionPoolGroups); - SafeDispose(NumberOfActiveConnectionPools); - SafeDispose(NumberOfActiveConnections); - SafeDispose(NumberOfFreeConnections); - SafeDispose(NumberOfStasisConnections); - SafeDispose(NumberOfReclaimedConnections); - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - private void SafeDispose(Counter counter) - { // WebData 103603 - if (counter != null) - { - counter.Dispose(); - } - } - - [PrePrepareMethod] - void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs e) - { - if (e != null && e.IsTerminating) - { - Dispose(); - } - } - - [PrePrepareMethod] - void ExitEventHandler(object sender, EventArgs e) - { - Dispose(); - } - - [PrePrepareMethod] - void UnloadEventHandler(object sender, EventArgs e) - { - Dispose(); - } - } - - sealed internal class DbConnectionPoolCountersNoCounters : DbConnectionPoolCounters - { - - public static readonly DbConnectionPoolCountersNoCounters SingletonInstance = new DbConnectionPoolCountersNoCounters(); - - private DbConnectionPoolCountersNoCounters() : base() - { - } - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 5aaa4fdcd7..af3b2d6bdf 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -132,9 +132,7 @@ internal int Clear() if (pool != null) { DbConnectionFactory connectionFactory = pool.ConnectionFactory; -#if NETFRAMEWORK - connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement(); -#endif + connectionFactory.QueuePoolForRelease(pool, true); } } @@ -197,10 +195,8 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor newPool.Startup(); // must start pool before usage bool addResult = _poolCollection.TryAdd(currentIdentity, newPool); Debug.Assert(addResult, "No other pool with current identity should exist at this point"); - SqlClientEventSource.Log.EnterActiveConnectionPool(); -#if NETFRAMEWORK - connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Increment(); -#endif + SqlClientEventSource.Metrics.EnterActiveConnectionPool(); + pool = newPool; } else @@ -276,9 +272,7 @@ internal bool Prune() // pool into a list of pools to be released when they // are completely empty. DbConnectionFactory connectionFactory = pool.ConnectionFactory; -#if NETFRAMEWORK - connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement(); -#endif + connectionFactory.QueuePoolForRelease(pool, false); } else diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 8dd1a7d745..9db1c7a5ec 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -228,11 +228,8 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal } SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Added.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); } -#if NET - SqlClientEventSource.Log.EnterFreeConnection(); -#else - Pool.PerformanceCounters.NumberOfFreeConnections.Increment(); -#endif + + SqlClientEventSource.Metrics.EnterFreeConnection(); } internal void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) @@ -295,11 +292,8 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra // connections, we'll put it back... if (0 <= entry) { -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - Pool.PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); Pool.PutObjectFromTransactedPool(transactedObject); } } @@ -505,10 +499,6 @@ internal override bool IsRunning private int MinPoolSize => PoolGroupOptions.MinPoolSize; -#if NETFRAMEWORK - internal override DbConnectionPoolCounters PerformanceCounters => _connectionFactory.PerformanceCounters; -#endif - internal override DbConnectionPoolGroup PoolGroup => _connectionPoolGroup; internal override DbConnectionPoolGroupOptions PoolGroupOptions => _connectionPoolGroupOptions; @@ -559,11 +549,8 @@ private void CleanupCallback(object state) { Debug.Assert(obj != null, "null connection is not expected"); // If we obtained one from the old stack, destroy it. -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); // Transaction roots must survive even aging out (TxEnd event will clean them up). bool shouldDestroy = true; @@ -660,21 +647,15 @@ internal override void Clear() while (_stackNew.TryPop(out obj)) { Debug.Assert(obj != null, "null connection is not expected"); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); DestroyObject(obj); } while (_stackOld.TryPop(out obj)) { Debug.Assert(obj != null, "null connection is not expected"); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); DestroyObject(obj); } @@ -749,11 +730,8 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio } _objectList.Add(newObj); _totalObjects = _objectList.Count; -#if NET - SqlClientEventSource.Log.EnterPooledConnection(); -#else - PerformanceCounters.NumberOfPooledConnections.Increment(); // TODO: Performance: Consider moving outside of lock? -#endif + + SqlClientEventSource.Metrics.EnterPooledConnection(); } SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Added to pool.", ObjectId, newObj?.ObjectID); @@ -981,19 +959,13 @@ internal override void DestroyObject(DbConnectionInternal obj) if (removed) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removed from pool.", ObjectId, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ExitPooledConnection(); -#else - PerformanceCounters.NumberOfPooledConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitPooledConnection(); } obj.Dispose(); SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Disposed.", ObjectId, obj.ObjectID); -#if NET - SqlClientEventSource.Log.HardDisconnectRequest(); -#else - PerformanceCounters.HardDisconnectsPerSecond.Increment(); -#endif + + SqlClientEventSource.Metrics.HardDisconnectRequest(); } } @@ -1199,9 +1171,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj { DbConnectionInternal obj = null; Transaction transaction = null; -#if NETFRAMEWORK - PerformanceCounters.SoftConnectsPerSecond.Increment(); -#endif + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Getting connection.", ObjectId); // If automatic transaction enlistment is required, then we try to @@ -1386,9 +1356,9 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj } connection = obj; -#if NET - SqlClientEventSource.Log.SoftConnectRequest(); -#endif + + SqlClientEventSource.Metrics.SoftConnectRequest(); + return true; } @@ -1420,17 +1390,12 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o /// A new inner connection that is attached to the internal override DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { -#if NETFRAMEWORK - PerformanceCounters.SoftConnectsPerSecond.Increment(); -#endif SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", ObjectId); DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); if (newConnection != null) { -#if NET - SqlClientEventSource.Log.SoftConnectRequest(); -#endif + SqlClientEventSource.Metrics.SoftConnectRequest(); PrepareConnection(owningObject, newConnection, oldConnection.EnlistedTransaction); oldConnection.PrepareForReplaceConnection(); oldConnection.DeactivateConnection(); @@ -1468,11 +1433,8 @@ private DbConnectionInternal GetFromGeneralPool() if (obj != null) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from general pool.", ObjectId, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); } return obj; } @@ -1489,11 +1451,8 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction) if (obj != null) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from transacted pool.", ObjectId, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); if (obj.IsTransactionRoot) { @@ -1662,11 +1621,8 @@ internal override void PutNewObject(DbConnectionInternal obj) _stackNew.Push(obj); _waitHandles.PoolSemaphore.Release(1); -#if NET - SqlClientEventSource.Log.EnterFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Increment(); -#endif + + SqlClientEventSource.Metrics.EnterFreeConnection(); } @@ -1674,11 +1630,7 @@ internal override void PutObject(DbConnectionInternal obj, object owningObject) { Debug.Assert(obj != null, "null obj?"); -#if NET - SqlClientEventSource.Log.SoftDisconnectRequest(); -#else - PerformanceCounters.SoftDisconnectsPerSecond.Increment(); -#endif + SqlClientEventSource.Metrics.SoftDisconnectRequest(); // Once a connection is closing (which is the state that we're in at // this point in time) you cannot delegate a transaction to or enlist @@ -1795,11 +1747,8 @@ private bool ReclaimEmancipatedObjects() { DbConnectionInternal obj = reclaimedObjects[i]; SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Reclaiming.", ObjectId, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ReclaimedConnectionRequest(); -#else - PerformanceCounters.NumberOfReclaimedConnections.Increment(); -#endif + + SqlClientEventSource.Metrics.ReclaimedConnectionRequest(); emancipatedObjectFound = true; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs new file mode 100644 index 0000000000..504d7507a2 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs @@ -0,0 +1,346 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Tracing; +#if NET +using System.Threading; +#endif + +#nullable enable + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + // This encapsulates three types of metrics: + // * Default: These metrics are always enabled. + // * Verbose: These metrics aren't enabled by default, but can be enabled on application startup if a trace switch is set to verbose. + // * Trace: These metrics are enabled with the SqlClientEventSource. + // Verbose metrics are only present for backwards compatibility. + internal sealed partial class SqlClientMetrics + { + private readonly SqlClientEventSource _eventSource; + + public SqlClientMetrics(SqlClientEventSource eventSource) + { + _eventSource = eventSource; + EnableDefaultMetrics(); + } + + private void EnableDefaultMetrics() + { +#if NETFRAMEWORK + EnablePerformanceCounters(); +#endif + } + + public void EnableTraceMetrics() + { +#if NET + EnableEventCounters(); +#endif + } + + /// + /// The number of actual connections that are being made to servers + /// + internal void HardConnectRequest() + { +#if NET + Interlocked.Increment(ref _activeHardConnectionsCounter); + Interlocked.Increment(ref _hardConnectsCounter); +#endif +#if NETFRAMEWORK + _hardConnectsPerSecond?.Increment(); +#endif + } + + /// + /// The number of actual disconnects that are being made to servers + /// + internal void HardDisconnectRequest() + { +#if NET + Interlocked.Decrement(ref _activeHardConnectionsCounter); + Interlocked.Increment(ref _hardDisconnectsCounter); +#endif +#if NETFRAMEWORK + _hardDisconnectsPerSecond?.Increment(); +#endif + } + + /// + /// The number of connections we get from the pool + /// + internal void SoftConnectRequest() + { +#if NET + Interlocked.Increment(ref _activeSoftConnectionsCounter); + Interlocked.Increment(ref _softConnectsCounter); +#endif +#if NETFRAMEWORK + _softConnectsPerSecond?.Increment(); +#endif + } + + /// + /// The number of connections we return to the pool + /// + internal void SoftDisconnectRequest() + { +#if NET + Interlocked.Decrement(ref _activeSoftConnectionsCounter); + Interlocked.Increment(ref _softDisconnectsCounter); +#endif +#if NETFRAMEWORK + _softDisconnectsPerSecond?.Increment(); +#endif + } + + /// + /// The number of connections that are not using connection pooling + /// + internal void EnterNonPooledConnection() + { +#if NET + Interlocked.Increment(ref _nonPooledConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfNonPooledConnections?.Increment(); +#endif + } + + /// + /// The number of connections that are not using connection pooling + /// + internal void ExitNonPooledConnection() + { +#if NET + Interlocked.Decrement(ref _nonPooledConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfNonPooledConnections?.Decrement(); +#endif + } + + /// + /// The number of connections that are managed by the connection pool + /// + internal void EnterPooledConnection() + { +#if NET + Interlocked.Increment(ref _pooledConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfPooledConnections?.Increment(); +#endif + } + + /// + /// The number of connections that are managed by the connection pool + /// + internal void ExitPooledConnection() + { +#if NET + Interlocked.Decrement(ref _pooledConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfPooledConnections?.Decrement(); +#endif + } + + /// + /// The number of unique connection strings + /// + internal void EnterActiveConnectionPoolGroup() + { +#if NET + Interlocked.Increment(ref _activeConnectionPoolGroupsCounter); +#endif +#if NETFRAMEWORK + _numberOfActiveConnectionPoolGroups?.Increment(); +#endif + } + + /// + /// The number of unique connection strings + /// + internal void ExitActiveConnectionPoolGroup() + { +#if NET + Interlocked.Decrement(ref _activeConnectionPoolGroupsCounter); +#endif +#if NETFRAMEWORK + _numberOfActiveConnectionPoolGroups?.Decrement(); +#endif + } + + /// + /// The number of unique connection strings waiting for pruning + /// + internal void EnterInactiveConnectionPoolGroup() + { +#if NET + Interlocked.Increment(ref _inactiveConnectionPoolGroupsCounter); +#endif +#if NETFRAMEWORK + _numberOfInactiveConnectionPoolGroups?.Increment(); +#endif + } + + /// + /// The number of unique connection strings waiting for pruning + /// + internal void ExitInactiveConnectionPoolGroup() + { +#if NET + Interlocked.Decrement(ref _inactiveConnectionPoolGroupsCounter); +#endif +#if NETFRAMEWORK + _numberOfInactiveConnectionPoolGroups?.Decrement(); +#endif + } + + /// + /// The number of connection pools + /// + internal void EnterActiveConnectionPool() + { +#if NET + Interlocked.Increment(ref _activeConnectionPoolsCounter); +#endif +#if NETFRAMEWORK + _numberOfActiveConnectionPools?.Increment(); +#endif + } + + /// + /// The number of connection pools + /// + internal void ExitActiveConnectionPool() + { +#if NET + Interlocked.Decrement(ref _activeConnectionPoolsCounter); +#endif +#if NETFRAMEWORK + _numberOfActiveConnectionPools?.Decrement(); +#endif + } + + /// + /// The number of connection pools + /// + internal void EnterInactiveConnectionPool() + { +#if NET + Interlocked.Increment(ref _inactiveConnectionPoolsCounter); +#endif +#if NETFRAMEWORK + _numberOfInactiveConnectionPools?.Increment(); +#endif + } + + /// + /// The number of connection pools + /// + internal void ExitInactiveConnectionPool() + { +#if NET + Interlocked.Decrement(ref _inactiveConnectionPoolsCounter); +#endif +#if NETFRAMEWORK + _numberOfInactiveConnectionPools?.Decrement(); +#endif + } + + /// + /// The number of connections currently in-use + /// + internal void EnterActiveConnection() + { +#if NET + Interlocked.Increment(ref _activeConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfActiveConnections?.Increment(); +#endif + } + + /// + /// The number of connections currently in-use + /// + internal void ExitActiveConnection() + { +#if NET + Interlocked.Decrement(ref _activeConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfActiveConnections?.Decrement(); +#endif + } + + /// + /// The number of connections currently available for use + /// + internal void EnterFreeConnection() + { +#if NET + Interlocked.Increment(ref _freeConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfFreeConnections?.Increment(); +#endif + } + + /// + /// The number of connections currently available for use + /// + internal void ExitFreeConnection() + { +#if NET + Interlocked.Decrement(ref _freeConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfFreeConnections?.Decrement(); +#endif + } + + /// + /// The number of connections currently waiting to be made ready for use + /// + internal void EnterStasisConnection() + { +#if NET + Interlocked.Increment(ref _stasisConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfStasisConnections?.Increment(); +#endif + } + + /// + /// The number of connections currently waiting to be made ready for use + /// + internal void ExitStasisConnection() + { +#if NET + Interlocked.Decrement(ref _stasisConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfStasisConnections?.Decrement(); +#endif + } + + /// + /// The number of connections we reclaim from GC'd external connections + /// + internal void ReclaimedConnectionRequest() + { +#if NET + Interlocked.Increment(ref _reclaimedConnectionsCounter); +#endif +#if NETFRAMEWORK + _numberOfReclaimedConnections?.Increment(); +#endif + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs new file mode 100644 index 0000000000..78ad032b38 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Tracing; + +#nullable enable + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + internal sealed partial class SqlClientMetrics + { + private PollingCounter? _activeHardConnections; + private IncrementingPollingCounter? _hardConnectsPerSecond; + private IncrementingPollingCounter? _hardDisconnectsPerSecond; + + private PollingCounter? _activeSoftConnections; + private IncrementingPollingCounter? _softConnects; + private IncrementingPollingCounter? _softDisconnects; + + private PollingCounter? _numberOfNonPooledConnections; + private PollingCounter? _numberOfPooledConnections; + + private PollingCounter? _numberOfActiveConnectionPoolGroups; + private PollingCounter? _numberOfInactiveConnectionPoolGroups; + + private PollingCounter? _numberOfActiveConnectionPools; + private PollingCounter? _numberOfInactiveConnectionPools; + + private PollingCounter? _numberOfActiveConnections; + private PollingCounter? _numberOfFreeConnections; + private PollingCounter? _numberOfStasisConnections; + private IncrementingPollingCounter? _numberOfReclaimedConnections; + + private long _activeHardConnectionsCounter = 0; + private long _hardConnectsCounter = 0; + private long _hardDisconnectsCounter = 0; + + private long _activeSoftConnectionsCounter = 0; + private long _softConnectsCounter = 0; + private long _softDisconnectsCounter = 0; + + private long _nonPooledConnectionsCounter = 0; + private long _pooledConnectionsCounter = 0; + + private long _activeConnectionPoolGroupsCounter = 0; + private long _inactiveConnectionPoolGroupsCounter = 0; + + private long _activeConnectionPoolsCounter = 0; + private long _inactiveConnectionPoolsCounter = 0; + + private long _activeConnectionsCounter = 0; + private long _freeConnectionsCounter = 0; + private long _stasisConnectionsCounter = 0; + private long _reclaimedConnectionsCounter = 0; + + private void EnableEventCounters() + { + _activeHardConnections = _activeHardConnections ?? + new PollingCounter("active-hard-connections", _eventSource, () => _activeHardConnectionsCounter) + { + DisplayName = "Actual active connections currently made to servers", + DisplayUnits = "count" + }; + + _hardConnectsPerSecond = _hardConnectsPerSecond ?? + new IncrementingPollingCounter("hard-connects", _eventSource, () => _hardConnectsCounter) + { + DisplayName = "Actual connection rate to servers", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _hardDisconnectsPerSecond = _hardDisconnectsPerSecond ?? + new IncrementingPollingCounter("hard-disconnects", _eventSource, () => _hardDisconnectsCounter) + { + DisplayName = "Actual disconnection rate from servers", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _activeSoftConnections = _activeSoftConnections ?? + new PollingCounter("active-soft-connects", _eventSource, () => _activeSoftConnectionsCounter) + { + DisplayName = "Active connections retrieved from the connection pool", + DisplayUnits = "count" + }; + + _softConnects = _softConnects ?? + new IncrementingPollingCounter("soft-connects", _eventSource, () => _softConnectsCounter) + { + DisplayName = "Rate of connections retrieved from the connection pool", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _softDisconnects = _softDisconnects ?? + new IncrementingPollingCounter("soft-disconnects", _eventSource, () => _softDisconnectsCounter) + { + DisplayName = "Rate of connections returned to the connection pool", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _numberOfNonPooledConnections = _numberOfNonPooledConnections ?? + new PollingCounter("number-of-non-pooled-connections", _eventSource, () => _nonPooledConnectionsCounter) + { + DisplayName = "Number of connections not using connection pooling", + DisplayUnits = "count" + }; + + _numberOfPooledConnections = _numberOfPooledConnections ?? + new PollingCounter("number-of-pooled-connections", _eventSource, () => _pooledConnectionsCounter) + { + DisplayName = "Number of connections managed by the connection pool", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionPoolGroups = _numberOfActiveConnectionPoolGroups ?? + new PollingCounter("number-of-active-connection-pool-groups", _eventSource, () => _activeConnectionPoolGroupsCounter) + { + DisplayName = "Number of active unique connection strings", + DisplayUnits = "count" + }; + + _numberOfInactiveConnectionPoolGroups = _numberOfInactiveConnectionPoolGroups ?? + new PollingCounter("number-of-inactive-connection-pool-groups", _eventSource, () => _inactiveConnectionPoolGroupsCounter) + { + DisplayName = "Number of unique connection strings waiting for pruning", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionPools = _numberOfActiveConnectionPools ?? + new PollingCounter("number-of-active-connection-pools", _eventSource, () => _activeConnectionPoolsCounter) + { + DisplayName = "Number of active connection pools", + DisplayUnits = "count" + }; + + _numberOfInactiveConnectionPools = _numberOfInactiveConnectionPools ?? + new PollingCounter("number-of-inactive-connection-pools", _eventSource, () => _inactiveConnectionPoolsCounter) + { + DisplayName = "Number of inactive connection pools", + DisplayUnits = "count" + }; + + _numberOfActiveConnections = _numberOfActiveConnections ?? + new PollingCounter("number-of-active-connections", _eventSource, () => _activeConnectionsCounter) + { + DisplayName = "Number of active connections", + DisplayUnits = "count" + }; + + _numberOfFreeConnections = _numberOfFreeConnections ?? + new PollingCounter("number-of-free-connections", _eventSource, () => _freeConnectionsCounter) + { + DisplayName = "Number of ready connections in the connection pool", + DisplayUnits = "count" + }; + + _numberOfStasisConnections = _numberOfStasisConnections ?? + new PollingCounter("number-of-stasis-connections", _eventSource, () => _stasisConnectionsCounter) + { + DisplayName = "Number of connections currently waiting to be ready", + DisplayUnits = "count" + }; + + _numberOfReclaimedConnections = _numberOfReclaimedConnections ?? + new IncrementingPollingCounter("number-of-reclaimed-connections", _eventSource, () => _reclaimedConnectionsCounter) + { + DisplayName = "Number of reclaimed connections from GC", + DisplayUnits = "count", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs new file mode 100644 index 0000000000..f6b822a5ba --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Versioning; +using System.Security.Permissions; +using Interop.Windows.Kernel32; +using Microsoft.Data.Common; + +#nullable enable + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] + internal sealed partial class SqlClientMetrics + { + private const string PerformanceCounterCategoryName = ".NET Data Provider for SqlServer"; + private const string PerformanceCounterCategoryHelp = "Counters for Microsoft.Data.SqlClient"; + private const int CounterInstanceNameMaxLength = 127; + + private PerformanceCounter? _hardConnectsPerSecond; + private PerformanceCounter? _hardDisconnectsPerSecond; + private PerformanceCounter? _softConnectsPerSecond; + private PerformanceCounter? _softDisconnectsPerSecond; + private PerformanceCounter? _numberOfNonPooledConnections; + private PerformanceCounter? _numberOfPooledConnections; + private PerformanceCounter? _numberOfActiveConnectionPoolGroups; + private PerformanceCounter? _numberOfInactiveConnectionPoolGroups; + private PerformanceCounter? _numberOfActiveConnectionPools; + private PerformanceCounter? _numberOfInactiveConnectionPools; + private PerformanceCounter? _numberOfActiveConnections; + private PerformanceCounter? _numberOfFreeConnections; + private PerformanceCounter? _numberOfStasisConnections; + private PerformanceCounter? _numberOfReclaimedConnections; + + private static PerformanceCounter? CreatePerformanceCounter(string categoryName, string instanceName, string counterName, PerformanceCounterType counterType) + { + PerformanceCounter? instance = null; + + try + { + instance = new PerformanceCounter(); + instance.CategoryName = categoryName; + instance.CounterName = counterName; + instance.InstanceName = instanceName; + instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process; + instance.ReadOnly = false; + instance.RawValue = 0; // make sure we start out at zero + } + catch (InvalidOperationException e) + { + ADP.TraceExceptionWithoutRethrow(e); + } + + return instance; + } + + [FileIOPermission(SecurityAction.Assert, Unrestricted = true)] + private static string? GetAssemblyName() + { + // First try GetEntryAssembly name, then AppDomain.FriendlyName. + Assembly? assembly = Assembly.GetEntryAssembly(); + AssemblyName? name = assembly.GetName(); + + return name?.Name; + } + + // SxS: this method uses GetCurrentProcessId to construct the instance name. + // TODO: VSDD 534795 - remove the Resource* attributes if you do not use GetCurrentProcessId after the fix + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] + private static string GetInstanceName() + { + string result; + string? instanceName = GetAssemblyName(); // instance perfcounter name + + if (string.IsNullOrEmpty(instanceName)) + { + instanceName = AppDomain.CurrentDomain?.FriendlyName; + } + + // TODO: If you do not use GetCurrentProcessId after fixing VSDD 534795, please remove Resource* attributes from this method + int pid = Kernel32Safe.GetCurrentProcessId(); + + // SQLBUDT #366157 -there are several characters which have special meaning + // to PERFMON. They recommend that we translate them as shown below, to + // prevent problems. + + result = string.Format(null, "{0}[{1}]", instanceName, pid); + result = result.Replace('(', '[').Replace(')', ']').Replace('#', '_').Replace('/', '_').Replace('\\', '_'); + + // SQLBUVSTS #94625 - counter instance name cannot be greater than 127 + if (result.Length > CounterInstanceNameMaxLength) + { + // Replacing the middle part with "[...]" + // For example: if path is c:\long_path\very_(Ax200)_long__path\perftest.exe and process ID is 1234 than the resulted instance name will be: + // c:\long_path\very_(AxM)[...](AxN)_long__path\perftest.exe[1234] + // while M and N are adjusted to make each part before and after the [...] = 61 (making the total = 61 + 5 + 61 = 127) + const string insertString = "[...]"; + int firstPartLength = (CounterInstanceNameMaxLength - insertString.Length) / 2; + int lastPartLength = CounterInstanceNameMaxLength - firstPartLength - insertString.Length; + result = string.Format(null, "{0}{1}{2}", + result.Substring(0, firstPartLength), + insertString, + result.Substring(result.Length - lastPartLength, lastPartLength)); + + Debug.Assert(result.Length == CounterInstanceNameMaxLength, + string.Format(null, "wrong calculation of the instance name: expected {0}, actual: {1}", CounterInstanceNameMaxLength, result.Length)); + } + + return result; + } + + [PerformanceCounterPermission(SecurityAction.Assert, PermissionAccess = PerformanceCounterPermissionAccess.Write, + MachineName = ".", CategoryName = PerformanceCounterCategoryName)] + private void EnablePerformanceCounters() + { + AppDomain.CurrentDomain.DomainUnload += UnloadEventHandler; + AppDomain.CurrentDomain.ProcessExit += ExitEventHandler; + AppDomain.CurrentDomain.UnhandledException += ExceptionEventHandler; + + string instanceName = GetInstanceName(); + + // level 0-3: hard connects/disconnects, plus basic pool/pool entry statistics + + _hardConnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "HardConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _hardDisconnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "HardDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _numberOfNonPooledConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfNonPooledConnections", PerformanceCounterType.NumberOfItems32); + _numberOfPooledConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfPooledConnections", PerformanceCounterType.NumberOfItems32); + _numberOfActiveConnectionPoolGroups = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfActiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); + _numberOfInactiveConnectionPoolGroups = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfInactiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); + _numberOfActiveConnectionPools = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfActiveConnectionPools", PerformanceCounterType.NumberOfItems32); + _numberOfInactiveConnectionPools = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfInactiveConnectionPools", PerformanceCounterType.NumberOfItems32); + _numberOfStasisConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfStasisConnections", PerformanceCounterType.NumberOfItems32); + _numberOfReclaimedConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfReclaimedConnections", PerformanceCounterType.NumberOfItems32); + + TraceSwitch perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); + if (TraceLevel.Verbose == perfCtrSwitch.Level) + { + _softConnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "SoftConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _softDisconnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "SoftDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _numberOfActiveConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfActiveConnections", PerformanceCounterType.NumberOfItems32); + _numberOfFreeConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfFreeConnections", PerformanceCounterType.NumberOfItems32); + } + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private void RemovePerformanceCounters() + { + // ExceptionEventHandler with IsTerminating may be called before + // the Connection Close is called or the variables are initialized + _hardConnectsPerSecond?.RemoveInstance(); + _hardDisconnectsPerSecond?.RemoveInstance(); + _softConnectsPerSecond?.RemoveInstance(); + _softDisconnectsPerSecond?.RemoveInstance(); + _numberOfNonPooledConnections?.RemoveInstance(); + _numberOfPooledConnections?.RemoveInstance(); + _numberOfActiveConnectionPoolGroups?.RemoveInstance(); + _numberOfInactiveConnectionPoolGroups?.RemoveInstance(); + _numberOfActiveConnectionPools?.RemoveInstance(); + _numberOfInactiveConnectionPools?.RemoveInstance(); + _numberOfActiveConnections?.RemoveInstance(); + _numberOfFreeConnections?.RemoveInstance(); + _numberOfStasisConnections?.RemoveInstance(); + _numberOfReclaimedConnections?.RemoveInstance(); + } + + [PrePrepareMethod] + private void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs e) + { + if (e != null && e.IsTerminating) + { + RemovePerformanceCounters(); + } + } + + [PrePrepareMethod] + private void ExitEventHandler(object sender, EventArgs e) + { + RemovePerformanceCounters(); + } + + [PrePrepareMethod] + private void UnloadEventHandler(object sender, EventArgs e) + { + RemovePerformanceCounters(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs index c794cb0d2a..6a8d503e53 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Data.SqlClient.Diagnostics; using System; using System.Diagnostics.Tracing; using System.Text; @@ -9,78 +10,32 @@ namespace Microsoft.Data.SqlClient { - internal abstract class SqlClientEventSourceBase : EventSource - { - protected override void OnEventCommand(EventCommandEventArgs command) - { - base.OnEventCommand(command); - EventCommandMethodCall(command); - } - - protected virtual void EventCommandMethodCall(EventCommandEventArgs command) { } - - #region not implemented for .Net core 2.1, .Net standard 2.0 and lower - internal virtual void HardConnectRequest() { /*no-op*/ } - - internal virtual void HardDisconnectRequest() { /*no-op*/ } - - internal virtual void SoftConnectRequest() { /*no-op*/ } - - internal virtual void SoftDisconnectRequest() { /*no-op*/ } - - internal virtual void EnterNonPooledConnection() { /*no-op*/ } - - internal virtual void ExitNonPooledConnection() { /*no-op*/ } - - internal virtual void EnterPooledConnection() { /*no-op*/ } - - internal virtual void ExitPooledConnection() { /*no-op*/ } - - internal virtual void EnterActiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void ExitActiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void EnterInactiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void ExitInactiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void EnterActiveConnectionPool() { /*no-op*/ } - - internal virtual void ExitActiveConnectionPool() { /*no-op*/ } - - internal virtual void EnterInactiveConnectionPool() { /*no-op*/ } - - internal virtual void ExitInactiveConnectionPool() { /*no-op*/ } - - internal virtual void EnterActiveConnection() { /*no-op*/ } - - internal virtual void ExitActiveConnection() { /*no-op*/ } - - internal virtual void EnterFreeConnection() { /*no-op*/ } - - internal virtual void ExitFreeConnection() { /*no-op*/ } - - internal virtual void EnterStasisConnection() { /*no-op*/ } - - internal virtual void ExitStasisConnection() { /*no-op*/ } - - internal virtual void ReclaimedConnectionRequest() { /*no-op*/ } - #endregion - } - // Any changes to event writers might be considered as a breaking change. // Other libraries such as OpenTelemetry and ApplicationInsight have based part of their code on BeginExecute and EndExecute arguments number. [EventSource(Name = "Microsoft.Data.SqlClient.EventSource")] - internal partial class SqlClientEventSource : SqlClientEventSourceBase + internal partial class SqlClientEventSource : EventSource { // Defines the singleton instance for the Resources ETW provider - internal static readonly SqlClientEventSource Log = new(); + public static readonly SqlClientEventSource Log = new(); + + // Provides access to metrics. + public static readonly SqlClientMetrics Metrics = new SqlClientMetrics(Log); private SqlClientEventSource() { } private const string NullStr = "null"; private const string SqlCommand_ClassName = nameof(SqlCommand); + protected override void OnEventCommand(EventCommandEventArgs command) + { + base.OnEventCommand(command); + + if (command.Command == EventCommand.Enable) + { + Metrics.EnableTraceMetrics(); + } + } + #region Event IDs // Initialized static Scope IDs private static long s_nextScopeId = 0; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs index 7a8e4bfe1a..63c0cc0b85 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs @@ -296,58 +296,59 @@ static SqlClientEventSourceProps() Type sqlClientEventSourceType = Assembly.GetAssembly(typeof(SqlConnection))!.GetType("Microsoft.Data.SqlClient.SqlClientEventSource"); Debug.Assert(sqlClientEventSourceType != null); - FieldInfo logField = sqlClientEventSourceType.GetField("Log", BindingFlags.Static | BindingFlags.NonPublic); - Debug.Assert(logField != null); - s_log = logField.GetValue(null); + FieldInfo metricsField = sqlClientEventSourceType.GetField("Metrics", BindingFlags.Static | BindingFlags.Public); + Debug.Assert(metricsField != null); + Type sqlClientMetricsType = metricsField.FieldType; + s_log = metricsField.GetValue(null); BindingFlags _bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; _activeHardConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_activeHardConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_activeHardConnectionsCounter), _bindingFlags); Debug.Assert(_activeHardConnectionsCounter != null); _hardConnectsCounter = - sqlClientEventSourceType.GetField(nameof(_hardConnectsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_hardConnectsCounter), _bindingFlags); Debug.Assert(_hardConnectsCounter != null); _hardDisconnectsCounter = - sqlClientEventSourceType.GetField(nameof(_hardDisconnectsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_hardDisconnectsCounter), _bindingFlags); Debug.Assert(_hardDisconnectsCounter != null); _activeSoftConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_activeSoftConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_activeSoftConnectionsCounter), _bindingFlags); Debug.Assert(_activeSoftConnectionsCounter != null); _softConnectsCounter = - sqlClientEventSourceType.GetField(nameof(_softConnectsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_softConnectsCounter), _bindingFlags); Debug.Assert(_softConnectsCounter != null); _softDisconnectsCounter = - sqlClientEventSourceType.GetField(nameof(_softDisconnectsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_softDisconnectsCounter), _bindingFlags); Debug.Assert(_softDisconnectsCounter != null); _nonPooledConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_nonPooledConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_nonPooledConnectionsCounter), _bindingFlags); Debug.Assert(_nonPooledConnectionsCounter != null); _pooledConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_pooledConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_pooledConnectionsCounter), _bindingFlags); Debug.Assert(_pooledConnectionsCounter != null); _activeConnectionPoolGroupsCounter = - sqlClientEventSourceType.GetField(nameof(_activeConnectionPoolGroupsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_activeConnectionPoolGroupsCounter), _bindingFlags); Debug.Assert(_activeConnectionPoolGroupsCounter != null); _inactiveConnectionPoolGroupsCounter = - sqlClientEventSourceType.GetField(nameof(_inactiveConnectionPoolGroupsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_inactiveConnectionPoolGroupsCounter), _bindingFlags); Debug.Assert(_inactiveConnectionPoolGroupsCounter != null); _activeConnectionPoolsCounter = - sqlClientEventSourceType.GetField(nameof(_activeConnectionPoolsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_activeConnectionPoolsCounter), _bindingFlags); Debug.Assert(_activeConnectionPoolsCounter != null); _inactiveConnectionPoolsCounter = - sqlClientEventSourceType.GetField(nameof(_inactiveConnectionPoolsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_inactiveConnectionPoolsCounter), _bindingFlags); Debug.Assert(_inactiveConnectionPoolsCounter != null); _activeConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_activeConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_activeConnectionsCounter), _bindingFlags); Debug.Assert(_activeConnectionsCounter != null); _freeConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_freeConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_freeConnectionsCounter), _bindingFlags); Debug.Assert(_freeConnectionsCounter != null); _stasisConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_stasisConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_stasisConnectionsCounter), _bindingFlags); Debug.Assert(_stasisConnectionsCounter != null); _reclaimedConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_reclaimedConnectionsCounter), _bindingFlags); + sqlClientMetricsType.GetField(nameof(_reclaimedConnectionsCounter), _bindingFlags); Debug.Assert(_reclaimedConnectionsCounter != null); } From 06b837be43ddcbdf7e6dd04d79113e390c780a01 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 1 Apr 2025 22:09:58 +0100 Subject: [PATCH 2/5] Correct null coalescing behaviour --- .../Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs index f6b822a5ba..cd2e42731b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs @@ -64,7 +64,7 @@ internal sealed partial class SqlClientMetrics { // First try GetEntryAssembly name, then AppDomain.FriendlyName. Assembly? assembly = Assembly.GetEntryAssembly(); - AssemblyName? name = assembly.GetName(); + AssemblyName? name = assembly?.GetName(); return name?.Name; } From 9613c3a3577a4bce49268e85cab591ee3d2560ca Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 5 Apr 2025 11:10:50 +0100 Subject: [PATCH 3/5] Widened existing EventCounter tests to include PerformanceCounters --- ....Data.SqlClient.ManualTesting.Tests.csproj | 4 +- .../TracingTests/EventCounterTest.cs | 387 --------------- .../ManualTests/TracingTests/MetricsTest.cs | 441 ++++++++++++++++++ 3 files changed, 444 insertions(+), 388 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index bd5b6750f5..e3414ea7ee 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -249,8 +249,10 @@ + + + - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs deleted file mode 100644 index 63c0cc0b85..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs +++ /dev/null @@ -1,387 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -using System; -using System.Diagnostics; -using System.Reflection; -using System.Transactions; -using Xunit; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests -{ - /// - /// This unit test is just valid for .NetCore 3.0 and above - /// - public class EventCounterTest - { - public EventCounterTest() - { - ClearConnectionPools(); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_HardConnectionsCounters_Functional() - { - //create a non-pooled connection - var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = false }; - - var ahc = SqlClientEventSourceProps.ActiveHardConnections; - var npc = SqlClientEventSourceProps.NonPooledConnections; - - using (var conn = new SqlConnection(stringBuilder.ToString())) - { - //initially we have no open physical connections - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - - conn.Open(); - - //when the connection gets opened, the real physical connection appears - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(npc + 1, SqlClientEventSourceProps.NonPooledConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - - conn.Close(); - - //when the connection gets closed, the real physical connection is also closed - Assert.Equal(ahc, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - } - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_SoftConnectionsCounters_Functional() - { - //create a pooled connection - var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true }; - - var ahc = SqlClientEventSourceProps.ActiveHardConnections; - var asc = SqlClientEventSourceProps.ActiveSoftConnections; - var pc = SqlClientEventSourceProps.PooledConnections; - var npc = SqlClientEventSourceProps.NonPooledConnections; - var acp = SqlClientEventSourceProps.ActiveConnectionPools; - var ac = SqlClientEventSourceProps.ActiveConnections; - var fc = SqlClientEventSourceProps.FreeConnections; - - using (var conn = new SqlConnection(stringBuilder.ToString())) - { - //initially we have no open physical connections - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); - - conn.Open(); - - //when the connection gets opened, the real physical connection appears - //and the appropriate pooling infrastructure gets deployed - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); - Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); - Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); - Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); - Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); - Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); - - conn.Close(); - - //when the connection gets closed, the real physical connection gets returned to the pool - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(asc, SqlClientEventSourceProps.ActiveSoftConnections); - Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); - Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); - Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); - Assert.Equal(ac, SqlClientEventSourceProps.ActiveConnections); - Assert.Equal(fc + 1, SqlClientEventSourceProps.FreeConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); - } - - using (var conn2 = new SqlConnection(stringBuilder.ToString())) - { - conn2.Open(); - - //the next open connection will reuse the underlying physical connection - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); - Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); - Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); - Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); - Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); - Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); - } - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void EventCounter_StasisCounters_Functional() - { - var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = false, Enlist = false }; - - using (var conn = new SqlConnection(stringBuilder.ToString())) - using (new TransactionScope()) - { - conn.Open(); - conn.EnlistTransaction(System.Transactions.Transaction.Current); - conn.Close(); - - //when the connection gets closed, but the ambient transaction is still in prigress - //the physical connection gets in stasis, until the transaction ends - Assert.Equal(1, SqlClientEventSourceProps.StasisConnections); - } - - //when the transaction finally ends, the physical connection is returned from stasis - Assert.Equal(0, SqlClientEventSourceProps.StasisConnections); - } - - [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3031")] - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_ReclaimedConnectionsCounter_Functional() - { - // clean pools and pool groups - ClearConnectionPools(); - var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true, MaxPoolSize = 1 }; - stringBuilder.ConnectTimeout = Math.Max(stringBuilder.ConnectTimeout, 30); - - long rc = SqlClientEventSourceProps.ReclaimedConnections; - - int gcNumber = GC.GetGeneration(CreateEmancipatedConnection(stringBuilder.ToString())); - // Specifying the generation number makes it to run faster by avoiding a full GC process - GC.Collect(gcNumber); - GC.WaitForPendingFinalizers(); - System.Threading.Thread.Sleep(200); // give the pooler some time to reclaim the connection and avoid the conflict. - - using (SqlConnection conn = new SqlConnection(stringBuilder.ToString())) - { - conn.Open(); - - // when calling open, the connection could be reclaimed. - if (GC.GetGeneration(conn) == gcNumber) - { - Assert.Equal(rc + 1, SqlClientEventSourceProps.ReclaimedConnections); - } - else - { - Assert.Equal(rc, SqlClientEventSourceProps.ReclaimedConnections); - } - } - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_ConnectionPoolGroupsCounter_Functional() - { - SqlConnection.ClearAllPools(); - - var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true }; - - long acpg = SqlClientEventSourceProps.ActiveConnectionPoolGroups; - long iacpg = SqlClientEventSourceProps.InactiveConnectionPoolGroups; - - using (SqlConnection conn = new SqlConnection(stringBuilder.ToString())) - { - conn.Open(); - - // when calling open, we have 1 more active connection pool group - Assert.Equal(acpg + 1, SqlClientEventSourceProps.ActiveConnectionPoolGroups); - - conn.Close(); - } - - SqlConnection.ClearAllPools(); - - // poolGroup state is changed from Active to Idle - PruneConnectionPoolGroups(); - - // poolGroup state is changed from Idle to Disabled - PruneConnectionPoolGroups(); - Assert.Equal(acpg, SqlClientEventSourceProps.ActiveConnectionPoolGroups); - Assert.Equal(iacpg + 1, SqlClientEventSourceProps.InactiveConnectionPoolGroups); - - // Remove poolGroup from poolGroupsToRelease list - PruneConnectionPoolGroups(); - Assert.Equal(iacpg, SqlClientEventSourceProps.ActiveConnectionPoolGroups); - } - - private static InternalConnectionWrapper CreateEmancipatedConnection(string connectionString) - { - SqlConnection connection = new SqlConnection(connectionString); - connection.Open(); - return new InternalConnectionWrapper(connection); - } - - private void ClearConnectionPools() - { - //ClearAllPoos kills all the existing pooled connection thus deactivating all the active pools - long liveConnectionPools = SqlClientEventSourceProps.ActiveConnectionPools + - SqlClientEventSourceProps.InactiveConnectionPools; - SqlConnection.ClearAllPools(); - Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPools, 0, liveConnectionPools); - Assert.Equal(0, SqlClientEventSourceProps.ActiveConnectionPools); - - long icp = SqlClientEventSourceProps.InactiveConnectionPools; - - // The 1st PruneConnectionPoolGroups call cleans the dangling inactive connection pools. - PruneConnectionPoolGroups(); - // If the pool isn't empty, it's because there are active connections or distributed transactions that need it. - Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPools, 0, icp); - - //the 2nd call deactivates the dangling connection pool groups - long liveConnectionPoolGroups = SqlClientEventSourceProps.ActiveConnectionPoolGroups + - SqlClientEventSourceProps.InactiveConnectionPoolGroups; - long acpg = SqlClientEventSourceProps.ActiveConnectionPoolGroups; - PruneConnectionPoolGroups(); - Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPoolGroups, 0, liveConnectionPoolGroups); - // If the pool entry isn't empty, it's because there are active pools that need it. - Assert.InRange(SqlClientEventSourceProps.ActiveConnectionPoolGroups, 0, acpg); - - long icpg = SqlClientEventSourceProps.InactiveConnectionPoolGroups; - //the 3rd call cleans the dangling connection pool groups - PruneConnectionPoolGroups(); - Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPoolGroups, 0, icpg); - } - - private static void PruneConnectionPoolGroups() - { - FieldInfo connectionFactoryField = GetConnectionFactoryField(); - MethodInfo pruneConnectionPoolGroupsMethod = - connectionFactoryField.FieldType.GetMethod("PruneConnectionPoolGroups", - BindingFlags.NonPublic | BindingFlags.Instance); - Debug.Assert(pruneConnectionPoolGroupsMethod != null); - pruneConnectionPoolGroupsMethod.Invoke(connectionFactoryField.GetValue(null), new[] { (object)null }); - } - - private static FieldInfo GetConnectionFactoryField() - { - FieldInfo connectionFactoryField = - typeof(SqlConnection).GetField("s_connectionFactory", BindingFlags.Static | BindingFlags.NonPublic); - Debug.Assert(connectionFactoryField != null); - return connectionFactoryField; - } - } - - internal static class SqlClientEventSourceProps - { - private static readonly object s_log; - private static readonly FieldInfo _activeHardConnectionsCounter; - private static readonly FieldInfo _hardConnectsCounter; - private static readonly FieldInfo _hardDisconnectsCounter; - private static readonly FieldInfo _activeSoftConnectionsCounter; - private static readonly FieldInfo _softConnectsCounter; - private static readonly FieldInfo _softDisconnectsCounter; - private static readonly FieldInfo _nonPooledConnectionsCounter; - private static readonly FieldInfo _pooledConnectionsCounter; - private static readonly FieldInfo _activeConnectionPoolGroupsCounter; - private static readonly FieldInfo _inactiveConnectionPoolGroupsCounter; - private static readonly FieldInfo _activeConnectionPoolsCounter; - private static readonly FieldInfo _inactiveConnectionPoolsCounter; - private static readonly FieldInfo _activeConnectionsCounter; - private static readonly FieldInfo _freeConnectionsCounter; - private static readonly FieldInfo _stasisConnectionsCounter; - private static readonly FieldInfo _reclaimedConnectionsCounter; - - static SqlClientEventSourceProps() - { - Type sqlClientEventSourceType = - Assembly.GetAssembly(typeof(SqlConnection))!.GetType("Microsoft.Data.SqlClient.SqlClientEventSource"); - Debug.Assert(sqlClientEventSourceType != null); - FieldInfo metricsField = sqlClientEventSourceType.GetField("Metrics", BindingFlags.Static | BindingFlags.Public); - Debug.Assert(metricsField != null); - Type sqlClientMetricsType = metricsField.FieldType; - s_log = metricsField.GetValue(null); - - BindingFlags _bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; - _activeHardConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_activeHardConnectionsCounter), _bindingFlags); - Debug.Assert(_activeHardConnectionsCounter != null); - _hardConnectsCounter = - sqlClientMetricsType.GetField(nameof(_hardConnectsCounter), _bindingFlags); - Debug.Assert(_hardConnectsCounter != null); - _hardDisconnectsCounter = - sqlClientMetricsType.GetField(nameof(_hardDisconnectsCounter), _bindingFlags); - Debug.Assert(_hardDisconnectsCounter != null); - _activeSoftConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_activeSoftConnectionsCounter), _bindingFlags); - Debug.Assert(_activeSoftConnectionsCounter != null); - _softConnectsCounter = - sqlClientMetricsType.GetField(nameof(_softConnectsCounter), _bindingFlags); - Debug.Assert(_softConnectsCounter != null); - _softDisconnectsCounter = - sqlClientMetricsType.GetField(nameof(_softDisconnectsCounter), _bindingFlags); - Debug.Assert(_softDisconnectsCounter != null); - _nonPooledConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_nonPooledConnectionsCounter), _bindingFlags); - Debug.Assert(_nonPooledConnectionsCounter != null); - _pooledConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_pooledConnectionsCounter), _bindingFlags); - Debug.Assert(_pooledConnectionsCounter != null); - _activeConnectionPoolGroupsCounter = - sqlClientMetricsType.GetField(nameof(_activeConnectionPoolGroupsCounter), _bindingFlags); - Debug.Assert(_activeConnectionPoolGroupsCounter != null); - _inactiveConnectionPoolGroupsCounter = - sqlClientMetricsType.GetField(nameof(_inactiveConnectionPoolGroupsCounter), _bindingFlags); - Debug.Assert(_inactiveConnectionPoolGroupsCounter != null); - _activeConnectionPoolsCounter = - sqlClientMetricsType.GetField(nameof(_activeConnectionPoolsCounter), _bindingFlags); - Debug.Assert(_activeConnectionPoolsCounter != null); - _inactiveConnectionPoolsCounter = - sqlClientMetricsType.GetField(nameof(_inactiveConnectionPoolsCounter), _bindingFlags); - Debug.Assert(_inactiveConnectionPoolsCounter != null); - _activeConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_activeConnectionsCounter), _bindingFlags); - Debug.Assert(_activeConnectionsCounter != null); - _freeConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_freeConnectionsCounter), _bindingFlags); - Debug.Assert(_freeConnectionsCounter != null); - _stasisConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_stasisConnectionsCounter), _bindingFlags); - Debug.Assert(_stasisConnectionsCounter != null); - _reclaimedConnectionsCounter = - sqlClientMetricsType.GetField(nameof(_reclaimedConnectionsCounter), _bindingFlags); - Debug.Assert(_reclaimedConnectionsCounter != null); - } - - public static long ActiveHardConnections => (long)_activeHardConnectionsCounter.GetValue(s_log)!; - - public static long HardConnects => (long)_hardConnectsCounter.GetValue(s_log)!; - - public static long HardDisconnects => (long)_hardDisconnectsCounter.GetValue(s_log)!; - - public static long ActiveSoftConnections => (long)_activeSoftConnectionsCounter.GetValue(s_log)!; - - public static long SoftConnects => (long)_softConnectsCounter.GetValue(s_log)!; - - public static long SoftDisconnects => (long)_softDisconnectsCounter.GetValue(s_log)!; - - public static long NonPooledConnections => (long)_nonPooledConnectionsCounter.GetValue(s_log)!; - - public static long PooledConnections => (long)_pooledConnectionsCounter.GetValue(s_log)!; - - public static long ActiveConnectionPoolGroups => (long)_activeConnectionPoolGroupsCounter.GetValue(s_log)!; - - public static long InactiveConnectionPoolGroups => (long)_inactiveConnectionPoolGroupsCounter.GetValue(s_log)!; - - public static long ActiveConnectionPools => (long)_activeConnectionPoolsCounter.GetValue(s_log)!; - - public static long InactiveConnectionPools => (long)_inactiveConnectionPoolsCounter.GetValue(s_log)!; - - public static long ActiveConnections => (long)_activeConnectionsCounter.GetValue(s_log)!; - - public static long FreeConnections => (long)_freeConnectionsCounter.GetValue(s_log)!; - - public static long StasisConnections => (long)_stasisConnectionsCounter.GetValue(s_log)!; - - public static long ReclaimedConnections => (long)_reclaimedConnectionsCounter.GetValue(s_log)!; - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs new file mode 100644 index 0000000000..723be21bd5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs @@ -0,0 +1,441 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; +using System.Diagnostics; +using System.Reflection; +using System.Transactions; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + public class MetricsTest + { +#if NETFRAMEWORK + private readonly static TraceSwitch s_perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); +#endif + + public MetricsTest() + { + ClearConnectionPools(); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void NonPooledConnectionsCounters_Functional() + { + //create a non-pooled connection + var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = false }; + + var ahc = SqlClientEventSourceProps.ActiveHardConnections; + var npc = SqlClientEventSourceProps.NonPooledConnections; + + using (var conn = new SqlConnection(stringBuilder.ToString())) + { + if (SupportsActiveConnectionCounters) + { + //initially we have no open physical connections + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + } + + conn.Open(); + + //when the connection gets opened, the real physical connection appears + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + } + Assert.Equal(npc + 1, SqlClientEventSourceProps.NonPooledConnections); + + conn.Close(); + + //when the connection gets closed, the real physical connection is also closed + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + } + Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void PooledConnectionsCounters_Functional() + { + //create a pooled connection + var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true }; + + var ahc = SqlClientEventSourceProps.ActiveHardConnections; + var asc = SqlClientEventSourceProps.ActiveSoftConnections; + var pc = SqlClientEventSourceProps.PooledConnections; + var npc = SqlClientEventSourceProps.NonPooledConnections; + var acp = SqlClientEventSourceProps.ActiveConnectionPools; + var ac = SqlClientEventSourceProps.ActiveConnections; + var fc = SqlClientEventSourceProps.FreeConnections; + + using (var conn = new SqlConnection(stringBuilder.ToString())) + { + if (SupportsActiveConnectionCounters) + { + //initially we have no open physical connections + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } + + conn.Open(); + + //when the connection gets opened, the real physical connection appears + //and the appropriate pooling infrastructure gets deployed + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } + Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); + Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); + Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); + if (VerboseActiveConnectionCountersEnabled) + { + Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); + Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); + } + + conn.Close(); + + //when the connection gets closed, the real physical connection gets returned to the pool + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(asc, SqlClientEventSourceProps.ActiveSoftConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } + Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); + Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); + Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); + if (VerboseActiveConnectionCountersEnabled) + { + Assert.Equal(ac, SqlClientEventSourceProps.ActiveConnections); + Assert.Equal(fc + 1, SqlClientEventSourceProps.FreeConnections); + } + } + + using (var conn2 = new SqlConnection(stringBuilder.ToString())) + { + conn2.Open(); + + //the next open connection will reuse the underlying physical connection + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } + Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); + Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); + Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); + if (VerboseActiveConnectionCountersEnabled) + { + Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); + Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public void StasisCounters_Functional() + { + var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = false, Enlist = false }; + + using (var conn = new SqlConnection(stringBuilder.ToString())) + using (new TransactionScope()) + { + conn.Open(); + conn.EnlistTransaction(System.Transactions.Transaction.Current); + conn.Close(); + + //when the connection gets closed, but the ambient transaction is still in prigress + //the physical connection gets in stasis, until the transaction ends + Assert.Equal(1, SqlClientEventSourceProps.StasisConnections); + } + + //when the transaction finally ends, the physical connection is returned from stasis + Assert.Equal(0, SqlClientEventSourceProps.StasisConnections); + } + + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3031")] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void ReclaimedConnectionsCounter_Functional() + { + // clean pools and pool groups + ClearConnectionPools(); + var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true, MaxPoolSize = 1 }; + stringBuilder.ConnectTimeout = Math.Max(stringBuilder.ConnectTimeout, 30); + + long rc = SqlClientEventSourceProps.ReclaimedConnections; + + int gcNumber = GC.GetGeneration(CreateEmancipatedConnection(stringBuilder.ToString())); + // Specifying the generation number makes it to run faster by avoiding a full GC process + GC.Collect(gcNumber); + GC.WaitForPendingFinalizers(); + System.Threading.Thread.Sleep(200); // give the pooler some time to reclaim the connection and avoid the conflict. + + using (SqlConnection conn = new SqlConnection(stringBuilder.ToString())) + { + conn.Open(); + + // when calling open, the connection could be reclaimed. + if (GC.GetGeneration(conn) == gcNumber) + { + Assert.Equal(rc + 1, SqlClientEventSourceProps.ReclaimedConnections); + } + else + { + Assert.Equal(rc, SqlClientEventSourceProps.ReclaimedConnections); + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void ConnectionPoolGroupsCounter_Functional() + { + SqlConnection.ClearAllPools(); + + var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true }; + + long acpg = SqlClientEventSourceProps.ActiveConnectionPoolGroups; + long iacpg = SqlClientEventSourceProps.InactiveConnectionPoolGroups; + + using (SqlConnection conn = new SqlConnection(stringBuilder.ToString())) + { + conn.Open(); + + // when calling open, we have 1 more active connection pool group + Assert.Equal(acpg + 1, SqlClientEventSourceProps.ActiveConnectionPoolGroups); + + conn.Close(); + } + + SqlConnection.ClearAllPools(); + + // poolGroup state is changed from Active to Idle + PruneConnectionPoolGroups(); + + // poolGroup state is changed from Idle to Disabled + PruneConnectionPoolGroups(); + Assert.Equal(acpg, SqlClientEventSourceProps.ActiveConnectionPoolGroups); + Assert.Equal(iacpg + 1, SqlClientEventSourceProps.InactiveConnectionPoolGroups); + + // Remove poolGroup from poolGroupsToRelease list + PruneConnectionPoolGroups(); + Assert.Equal(iacpg, SqlClientEventSourceProps.ActiveConnectionPoolGroups); + } + + private static InternalConnectionWrapper CreateEmancipatedConnection(string connectionString) + { + SqlConnection connection = new SqlConnection(connectionString); + connection.Open(); + return new InternalConnectionWrapper(connection); + } + + private void ClearConnectionPools() + { + //ClearAllPoos kills all the existing pooled connection thus deactivating all the active pools + long liveConnectionPools = SqlClientEventSourceProps.ActiveConnectionPools + + SqlClientEventSourceProps.InactiveConnectionPools; + SqlConnection.ClearAllPools(); + Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPools, 0, liveConnectionPools); + Assert.Equal(0, SqlClientEventSourceProps.ActiveConnectionPools); + + long icp = SqlClientEventSourceProps.InactiveConnectionPools; + + // The 1st PruneConnectionPoolGroups call cleans the dangling inactive connection pools. + PruneConnectionPoolGroups(); + // If the pool isn't empty, it's because there are active connections or distributed transactions that need it. + Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPools, 0, icp); + + //the 2nd call deactivates the dangling connection pool groups + long liveConnectionPoolGroups = SqlClientEventSourceProps.ActiveConnectionPoolGroups + + SqlClientEventSourceProps.InactiveConnectionPoolGroups; + long acpg = SqlClientEventSourceProps.ActiveConnectionPoolGroups; + PruneConnectionPoolGroups(); + Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPoolGroups, 0, liveConnectionPoolGroups); + // If the pool entry isn't empty, it's because there are active pools that need it. + Assert.InRange(SqlClientEventSourceProps.ActiveConnectionPoolGroups, 0, acpg); + + long icpg = SqlClientEventSourceProps.InactiveConnectionPoolGroups; + //the 3rd call cleans the dangling connection pool groups + PruneConnectionPoolGroups(); + Assert.InRange(SqlClientEventSourceProps.InactiveConnectionPoolGroups, 0, icpg); + } + + private static void PruneConnectionPoolGroups() + { + FieldInfo connectionFactoryField = GetConnectionFactoryField(); + MethodInfo pruneConnectionPoolGroupsMethod = + connectionFactoryField.FieldType.GetMethod("PruneConnectionPoolGroups", + BindingFlags.NonPublic | BindingFlags.Instance); + Debug.Assert(pruneConnectionPoolGroupsMethod != null); + pruneConnectionPoolGroupsMethod.Invoke(connectionFactoryField.GetValue(null), new[] { (object)null }); + } + + private static FieldInfo GetConnectionFactoryField() + { +#if NET + FieldInfo connectionFactoryField = + typeof(SqlConnection).GetField("s_connectionFactory", BindingFlags.Static | BindingFlags.NonPublic); +#else + FieldInfo connectionFactoryField = + typeof(SqlConnection).GetField("_connectionFactory", BindingFlags.Static | BindingFlags.NonPublic); +#endif + Debug.Assert(connectionFactoryField != null); + return connectionFactoryField; + } + + // Only the .NET Core build supports the active-hard-connections and active-soft-connects counters. The .NET Framework + // build doesn't have comparable performance counters. + private static bool SupportsActiveConnectionCounters => +#if NET + true; +#else + false; +#endif + + private static bool VerboseActiveConnectionCountersEnabled => + SupportsActiveConnectionCounters || +#if NET + true; +#else + s_perfCtrSwitch.Level == TraceLevel.Verbose; +#endif + } + + internal static class SqlClientEventSourceProps + { + private static readonly object s_log; + private static readonly Func s_getActiveHardConnections; + private static readonly Func s_getHardConnects; + private static readonly Func s_getHardDisconnects; + private static readonly Func s_getActiveSoftConnections; + private static readonly Func s_getSoftConnects; + private static readonly Func s_getSoftDisconnects; + private static readonly Func s_getNonPooledConnections; + private static readonly Func s_getPooledConnections; + private static readonly Func s_getActiveConnectionPoolGroups; + private static readonly Func s_getInactiveConnectionPoolGroups; + private static readonly Func s_getActiveConnectionPools; + private static readonly Func s_getInactiveConnectionPools; + private static readonly Func s_getActiveConnections; + private static readonly Func s_getFreeConnections; + private static readonly Func s_getStasisConnections; + private static readonly Func s_getReclaimedConnections; + + static SqlClientEventSourceProps() + { + s_log = SqlClientEventSource.Metrics; + +#if NET + s_getActiveHardConnections = GenerateFieldGetter("_activeHardConnectionsCounter"); + s_getHardConnects = GenerateFieldGetter("_hardConnectsCounter"); + s_getHardDisconnects = GenerateFieldGetter("_hardDisconnectsCounter"); + s_getActiveSoftConnections = GenerateFieldGetter("_activeSoftConnectionsCounter"); + s_getSoftConnects = GenerateFieldGetter("_softConnectsCounter"); + s_getSoftDisconnects = GenerateFieldGetter("_softDisconnectsCounter"); + s_getNonPooledConnections = GenerateFieldGetter("_nonPooledConnectionsCounter"); + s_getPooledConnections = GenerateFieldGetter("_pooledConnectionsCounter"); + s_getActiveConnectionPoolGroups = GenerateFieldGetter("_activeConnectionPoolGroupsCounter"); + s_getInactiveConnectionPoolGroups = GenerateFieldGetter("_inactiveConnectionPoolGroupsCounter"); + s_getActiveConnectionPools = GenerateFieldGetter("_activeConnectionPoolsCounter"); + s_getInactiveConnectionPools = GenerateFieldGetter("_inactiveConnectionPoolsCounter"); + s_getActiveConnections = GenerateFieldGetter("_activeConnectionsCounter"); + s_getFreeConnections = GenerateFieldGetter("_freeConnectionsCounter"); + s_getStasisConnections = GenerateFieldGetter("_stasisConnectionsCounter"); + s_getReclaimedConnections = GenerateFieldGetter("_reclaimedConnectionsCounter"); + + static Func GenerateFieldGetter(string fieldName) + { + FieldInfo counterField = s_log.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + + Debug.Assert(counterField != null); + return () => (long)counterField.GetValue(s_log)!; + } +#else + Func notApplicableFunction = static () => -1; + + // .NET Framework doesn't have performance counters for the number of hard and soft connections. + s_getActiveHardConnections = notApplicableFunction; + s_getHardConnects = GeneratePerformanceCounterGetter("_hardConnectsPerSecond"); + s_getHardDisconnects = GeneratePerformanceCounterGetter("_hardDisconnectsPerSecond"); + s_getActiveSoftConnections = notApplicableFunction; + s_getSoftConnects = GeneratePerformanceCounterGetter("_softConnectsPerSecond"); + s_getSoftDisconnects = GeneratePerformanceCounterGetter("_softDisconnectsPerSecond"); + + s_getNonPooledConnections = GeneratePerformanceCounterGetter("_numberOfNonPooledConnections"); + s_getPooledConnections = GeneratePerformanceCounterGetter("_numberOfPooledConnections"); + s_getActiveConnectionPoolGroups = GeneratePerformanceCounterGetter("_numberOfActiveConnectionPoolGroups"); + s_getInactiveConnectionPoolGroups = GeneratePerformanceCounterGetter("_numberOfInactiveConnectionPoolGroups"); + s_getActiveConnectionPools = GeneratePerformanceCounterGetter("_numberOfActiveConnectionPools"); + s_getInactiveConnectionPools = GeneratePerformanceCounterGetter("_numberOfInactiveConnectionPools"); + s_getActiveConnections = GeneratePerformanceCounterGetter("_numberOfActiveConnections"); + s_getFreeConnections = GeneratePerformanceCounterGetter("_numberOfFreeConnections"); + s_getStasisConnections = GeneratePerformanceCounterGetter("_numberOfStasisConnections"); + s_getReclaimedConnections = GeneratePerformanceCounterGetter("_numberOfReclaimedConnections"); + + static Func GeneratePerformanceCounterGetter(string fieldName) + { + FieldInfo counterField = s_log.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + Debug.Assert(counterField != null); + + PerformanceCounter counter = counterField?.GetValue(s_log) as PerformanceCounter; + return () => counter is null ? -1 : counter.RawValue; + } +#endif + } + + public static long ActiveHardConnections => s_getActiveHardConnections(); + + public static long HardConnects => s_getHardConnects(); + + public static long HardDisconnects => s_getHardDisconnects(); + + public static long ActiveSoftConnections => s_getActiveSoftConnections(); + + public static long SoftConnects => s_getSoftConnects(); + + public static long SoftDisconnects => s_getSoftDisconnects(); + + public static long NonPooledConnections => s_getNonPooledConnections(); + + public static long PooledConnections => s_getPooledConnections(); + + public static long ActiveConnectionPoolGroups => s_getActiveConnectionPoolGroups(); + + public static long InactiveConnectionPoolGroups => s_getInactiveConnectionPoolGroups(); + + public static long ActiveConnectionPools => s_getActiveConnectionPools(); + + public static long InactiveConnectionPools => s_getInactiveConnectionPools(); + + public static long ActiveConnections => s_getActiveConnections(); + + public static long FreeConnections => s_getFreeConnections(); + + public static long StasisConnections => s_getStasisConnections(); + + public static long ReclaimedConnections => s_getReclaimedConnections(); + } +} From f6d3a6b146d586b787ed98e7e3e2b30e048bb81b Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 7 Apr 2025 22:19:32 +0100 Subject: [PATCH 4/5] Merge into one file, reduce conditional compilation branches --- .../src/Microsoft.Data.SqlClient.csproj | 3 - .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 - .../SqlClient/Diagnostics/SqlClientMetrics.cs | 516 +++++++++++++----- .../Diagnostics/SqlClientMetrics.netcore.cs | 178 ------ .../Diagnostics/SqlClientMetrics.netfx.cs | 193 ------- .../Data/SqlClient/SqlClientEventSource.cs | 4 +- .../ManualTests/TracingTests/MetricsTest.cs | 64 +-- 7 files changed, 398 insertions(+), 563 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index e90942685b..c1a80217fd 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -215,9 +215,6 @@ Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs - - Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.netcore.cs - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitAfter.netcore.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index d6034754e9..13d68edbbb 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -310,9 +310,6 @@ Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs - - Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.netfx.cs - Microsoft\Data\ProviderBase\DbMetaDataFactory.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs index 504d7507a2..f62c65b122 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs @@ -2,43 +2,123 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Diagnostics.Tracing; + #if NET using System.Threading; +#else +using Interop.Windows.Kernel32; +using Microsoft.Data.Common; + +using System.Diagnostics; +using System.Reflection; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Versioning; +using System.Security.Permissions; #endif #nullable enable namespace Microsoft.Data.SqlClient.Diagnostics { - // This encapsulates three types of metrics: - // * Default: These metrics are always enabled. - // * Verbose: These metrics aren't enabled by default, but can be enabled on application startup if a trace switch is set to verbose. - // * Trace: These metrics are enabled with the SqlClientEventSource. - // Verbose metrics are only present for backwards compatibility. +#if NETFRAMEWORK + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] +#endif internal sealed partial class SqlClientMetrics { +#if NETFRAMEWORK + private const string PerformanceCounterCategoryName = ".NET Data Provider for SqlServer"; + private const string PerformanceCounterCategoryHelp = "Counters for Microsoft.Data.SqlClient"; + private const int CounterInstanceNameMaxLength = 127; +#endif + private readonly SqlClientEventSource _eventSource; + // The names of the below variables must match between .NET Framework and .NET Core. +#if NET + private PollingCounter? _activeHardConnectionsCounter; + private long _activeHardConnections = 0; + private IncrementingPollingCounter? _hardConnectsPerSecondCounter; + private long _hardConnectsRate = 0; + private IncrementingPollingCounter? _hardDisconnectsPerSecondCounter; + private long _hardDisconnectsRate = 0; + + private PollingCounter? _activeSoftConnectionsCounter; + private long _activeSoftConnections = 0; + private IncrementingPollingCounter? _softConnectsPerSecondCounter; + private long _softConnectsRate = 0; + private IncrementingPollingCounter? _softDisconnectsPerSecondCounter; + private long _softDisconnectsRate = 0; + + private PollingCounter? _numberOfNonPooledConnectionsCounter; + private long _nonPooledConnections = 0; + private PollingCounter? _numberOfPooledConnectionsCounter; + private long _pooledConnections = 0; + + private PollingCounter? _numberOfActiveConnectionPoolGroupsCounter; + private long _activeConnectionPoolGroups = 0; + private PollingCounter? _numberOfInactiveConnectionPoolGroupsCounter; + private long _inactiveConnectionPoolGroups = 0; + + private PollingCounter? _numberOfActiveConnectionPoolsCounter; + private long _activeConnectionPools = 0; + private PollingCounter? _numberOfInactiveConnectionPoolsCounter; + private long _inactiveConnectionPools = 0; + + private PollingCounter? _numberOfActiveConnectionsCounter; + private long _activeConnections = 0; + private PollingCounter? _numberOfFreeConnectionsCounter; + private long _freeConnections = 0; + private PollingCounter? _numberOfStasisConnectionsCounter; + private long _stasisConnections = 0; + private IncrementingPollingCounter? _numberOfReclaimedConnectionsCounter; + private long _reclaimedConnections = 0; +#else + private PerformanceCounter? _hardConnectsRate; + private PerformanceCounter? _hardDisconnectsRate; + private PerformanceCounter? _softConnectsRate; + private PerformanceCounter? _softDisconnectsRate; + private PerformanceCounter? _nonPooledConnections; + private PerformanceCounter? _pooledConnections; + private PerformanceCounter? _activeConnectionPoolGroups; + private PerformanceCounter? _inactiveConnectionPoolGroups; + private PerformanceCounter? _activeConnectionPools; + private PerformanceCounter? _inactiveConnectionPools; + private PerformanceCounter? _activeConnections; + private PerformanceCounter? _freeConnections; + private PerformanceCounter? _stasisConnections; + private PerformanceCounter? _reclaimedConnections; + + private string? _instanceName; +#endif public SqlClientMetrics(SqlClientEventSource eventSource) { _eventSource = eventSource; - EnableDefaultMetrics(); - } - private void EnableDefaultMetrics() - { #if NETFRAMEWORK + // On .NET Framework, metrics are exposed as performance counters and are always enabled. + // On .NET Core, metrics are exposed as EventCounters, and require explicit enablement. EnablePerformanceCounters(); #endif } - public void EnableTraceMetrics() - { #if NET - EnableEventCounters(); + private static void IncrementPlatformSpecificCounter(ref long counter) + => Interlocked.Increment(ref counter); + + private static void DecrementPlatformSpecificCounter(ref long counter) + => Interlocked.Decrement(ref counter); +#else + // .NET Framework doesn't strictly require the PerformanceCounter parameter to be passed as a ref, but doing + // so means that IncrementPlatformSpecificCounter and DecrementPlatformSpecificCounter can be called in identical + // ways between .NET Framework and .NET Core. + private static void IncrementPlatformSpecificCounter(ref PerformanceCounter? counter) + => counter?.Increment(); + + private static void DecrementPlatformSpecificCounter(ref PerformanceCounter? counter) + => counter?.Decrement(); #endif - } /// /// The number of actual connections that are being made to servers @@ -46,12 +126,9 @@ public void EnableTraceMetrics() internal void HardConnectRequest() { #if NET - Interlocked.Increment(ref _activeHardConnectionsCounter); - Interlocked.Increment(ref _hardConnectsCounter); -#endif -#if NETFRAMEWORK - _hardConnectsPerSecond?.Increment(); + IncrementPlatformSpecificCounter(ref _activeHardConnections); #endif + IncrementPlatformSpecificCounter(ref _hardConnectsRate); } /// @@ -60,12 +137,9 @@ internal void HardConnectRequest() internal void HardDisconnectRequest() { #if NET - Interlocked.Decrement(ref _activeHardConnectionsCounter); - Interlocked.Increment(ref _hardDisconnectsCounter); -#endif -#if NETFRAMEWORK - _hardDisconnectsPerSecond?.Increment(); + DecrementPlatformSpecificCounter(ref _activeHardConnections); #endif + IncrementPlatformSpecificCounter(ref _hardDisconnectsRate); } /// @@ -74,12 +148,9 @@ internal void HardDisconnectRequest() internal void SoftConnectRequest() { #if NET - Interlocked.Increment(ref _activeSoftConnectionsCounter); - Interlocked.Increment(ref _softConnectsCounter); -#endif -#if NETFRAMEWORK - _softConnectsPerSecond?.Increment(); + IncrementPlatformSpecificCounter(ref _activeSoftConnections); #endif + IncrementPlatformSpecificCounter(ref _softConnectsRate); } /// @@ -88,12 +159,9 @@ internal void SoftConnectRequest() internal void SoftDisconnectRequest() { #if NET - Interlocked.Decrement(ref _activeSoftConnectionsCounter); - Interlocked.Increment(ref _softDisconnectsCounter); -#endif -#if NETFRAMEWORK - _softDisconnectsPerSecond?.Increment(); + DecrementPlatformSpecificCounter(ref _activeSoftConnections); #endif + IncrementPlatformSpecificCounter(ref _softDisconnectsRate); } /// @@ -101,12 +169,7 @@ internal void SoftDisconnectRequest() /// internal void EnterNonPooledConnection() { -#if NET - Interlocked.Increment(ref _nonPooledConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfNonPooledConnections?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _nonPooledConnections); } /// @@ -114,12 +177,7 @@ internal void EnterNonPooledConnection() /// internal void ExitNonPooledConnection() { -#if NET - Interlocked.Decrement(ref _nonPooledConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfNonPooledConnections?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _nonPooledConnections); } /// @@ -127,12 +185,7 @@ internal void ExitNonPooledConnection() /// internal void EnterPooledConnection() { -#if NET - Interlocked.Increment(ref _pooledConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfPooledConnections?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _pooledConnections); } /// @@ -140,12 +193,7 @@ internal void EnterPooledConnection() /// internal void ExitPooledConnection() { -#if NET - Interlocked.Decrement(ref _pooledConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfPooledConnections?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _pooledConnections); } /// @@ -153,12 +201,7 @@ internal void ExitPooledConnection() /// internal void EnterActiveConnectionPoolGroup() { -#if NET - Interlocked.Increment(ref _activeConnectionPoolGroupsCounter); -#endif -#if NETFRAMEWORK - _numberOfActiveConnectionPoolGroups?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _activeConnectionPoolGroups); } /// @@ -166,12 +209,7 @@ internal void EnterActiveConnectionPoolGroup() /// internal void ExitActiveConnectionPoolGroup() { -#if NET - Interlocked.Decrement(ref _activeConnectionPoolGroupsCounter); -#endif -#if NETFRAMEWORK - _numberOfActiveConnectionPoolGroups?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _activeConnectionPoolGroups); } /// @@ -179,12 +217,7 @@ internal void ExitActiveConnectionPoolGroup() /// internal void EnterInactiveConnectionPoolGroup() { -#if NET - Interlocked.Increment(ref _inactiveConnectionPoolGroupsCounter); -#endif -#if NETFRAMEWORK - _numberOfInactiveConnectionPoolGroups?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _inactiveConnectionPoolGroups); } /// @@ -192,12 +225,7 @@ internal void EnterInactiveConnectionPoolGroup() /// internal void ExitInactiveConnectionPoolGroup() { -#if NET - Interlocked.Decrement(ref _inactiveConnectionPoolGroupsCounter); -#endif -#if NETFRAMEWORK - _numberOfInactiveConnectionPoolGroups?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _inactiveConnectionPoolGroups); } /// @@ -205,12 +233,7 @@ internal void ExitInactiveConnectionPoolGroup() /// internal void EnterActiveConnectionPool() { -#if NET - Interlocked.Increment(ref _activeConnectionPoolsCounter); -#endif -#if NETFRAMEWORK - _numberOfActiveConnectionPools?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _activeConnectionPools); } /// @@ -218,12 +241,7 @@ internal void EnterActiveConnectionPool() /// internal void ExitActiveConnectionPool() { -#if NET - Interlocked.Decrement(ref _activeConnectionPoolsCounter); -#endif -#if NETFRAMEWORK - _numberOfActiveConnectionPools?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _activeConnectionPools); } /// @@ -231,12 +249,7 @@ internal void ExitActiveConnectionPool() /// internal void EnterInactiveConnectionPool() { -#if NET - Interlocked.Increment(ref _inactiveConnectionPoolsCounter); -#endif -#if NETFRAMEWORK - _numberOfInactiveConnectionPools?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _inactiveConnectionPools); } /// @@ -244,12 +257,7 @@ internal void EnterInactiveConnectionPool() /// internal void ExitInactiveConnectionPool() { -#if NET - Interlocked.Decrement(ref _inactiveConnectionPoolsCounter); -#endif -#if NETFRAMEWORK - _numberOfInactiveConnectionPools?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _inactiveConnectionPools); } /// @@ -257,12 +265,7 @@ internal void ExitInactiveConnectionPool() /// internal void EnterActiveConnection() { -#if NET - Interlocked.Increment(ref _activeConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfActiveConnections?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _activeConnections); } /// @@ -270,12 +273,7 @@ internal void EnterActiveConnection() /// internal void ExitActiveConnection() { -#if NET - Interlocked.Decrement(ref _activeConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfActiveConnections?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _activeConnections); } /// @@ -283,12 +281,7 @@ internal void ExitActiveConnection() /// internal void EnterFreeConnection() { -#if NET - Interlocked.Increment(ref _freeConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfFreeConnections?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _freeConnections); } /// @@ -296,12 +289,7 @@ internal void EnterFreeConnection() /// internal void ExitFreeConnection() { -#if NET - Interlocked.Decrement(ref _freeConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfFreeConnections?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _freeConnections); } /// @@ -309,12 +297,7 @@ internal void ExitFreeConnection() /// internal void EnterStasisConnection() { -#if NET - Interlocked.Increment(ref _stasisConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfStasisConnections?.Increment(); -#endif + IncrementPlatformSpecificCounter(ref _stasisConnections); } /// @@ -322,12 +305,7 @@ internal void EnterStasisConnection() /// internal void ExitStasisConnection() { -#if NET - Interlocked.Decrement(ref _stasisConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfStasisConnections?.Decrement(); -#endif + DecrementPlatformSpecificCounter(ref _stasisConnections); } /// @@ -335,12 +313,258 @@ internal void ExitStasisConnection() /// internal void ReclaimedConnectionRequest() { + IncrementPlatformSpecificCounter(ref _reclaimedConnections); + } + #if NET - Interlocked.Increment(ref _reclaimedConnectionsCounter); -#endif -#if NETFRAMEWORK - _numberOfReclaimedConnections?.Increment(); -#endif + public void EnableEventCounters() + { + _activeHardConnectionsCounter ??= new PollingCounter("active-hard-connections", _eventSource, () => _activeHardConnections) + { + DisplayName = "Actual active connections currently made to servers", + DisplayUnits = "count" + }; + + _hardConnectsPerSecondCounter ??= new IncrementingPollingCounter("hard-connects", _eventSource, () => _hardConnectsRate) + { + DisplayName = "Actual connection rate to servers", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _hardDisconnectsPerSecondCounter ??= new IncrementingPollingCounter("hard-disconnects", _eventSource, () => _hardDisconnectsRate) + { + DisplayName = "Actual disconnection rate from servers", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _activeSoftConnectionsCounter ??= new PollingCounter("active-soft-connects", _eventSource, () => _activeSoftConnections) + { + DisplayName = "Active connections retrieved from the connection pool", + DisplayUnits = "count" + }; + + _softConnectsPerSecondCounter ??= new IncrementingPollingCounter("soft-connects", _eventSource, () => _softConnectsRate) + { + DisplayName = "Rate of connections retrieved from the connection pool", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _softDisconnectsPerSecondCounter ??= new IncrementingPollingCounter("soft-disconnects", _eventSource, () => _softDisconnectsRate) + { + DisplayName = "Rate of connections returned to the connection pool", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _numberOfNonPooledConnectionsCounter ??= new PollingCounter("number-of-non-pooled-connections", _eventSource, () => _nonPooledConnections) + { + DisplayName = "Number of connections not using connection pooling", + DisplayUnits = "count" + }; + + _numberOfPooledConnectionsCounter ??= new PollingCounter("number-of-pooled-connections", _eventSource, () => _pooledConnections) + { + DisplayName = "Number of connections managed by the connection pool", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionPoolGroupsCounter ??= new PollingCounter("number-of-active-connection-pool-groups", _eventSource, () => _activeConnectionPoolGroups) + { + DisplayName = "Number of active unique connection strings", + DisplayUnits = "count" + }; + + _numberOfInactiveConnectionPoolGroupsCounter ??= new PollingCounter("number-of-inactive-connection-pool-groups", _eventSource, () => _inactiveConnectionPoolGroups) + { + DisplayName = "Number of unique connection strings waiting for pruning", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionPoolsCounter ??= new PollingCounter("number-of-active-connection-pools", _eventSource, () => _activeConnectionPools) + { + DisplayName = "Number of active connection pools", + DisplayUnits = "count" + }; + + _numberOfInactiveConnectionPoolsCounter ??= new PollingCounter("number-of-inactive-connection-pools", _eventSource, () => _inactiveConnectionPools) + { + DisplayName = "Number of inactive connection pools", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionsCounter ??= new PollingCounter("number-of-active-connections", _eventSource, () => _activeConnections) + { + DisplayName = "Number of active connections", + DisplayUnits = "count" + }; + + _numberOfFreeConnectionsCounter ??= new PollingCounter("number-of-free-connections", _eventSource, () => _freeConnections) + { + DisplayName = "Number of ready connections in the connection pool", + DisplayUnits = "count" + }; + + _numberOfStasisConnectionsCounter ??= new PollingCounter("number-of-stasis-connections", _eventSource, () => _stasisConnections) + { + DisplayName = "Number of connections currently waiting to be ready", + DisplayUnits = "count" + }; + + _numberOfReclaimedConnectionsCounter ??= new IncrementingPollingCounter("number-of-reclaimed-connections", _eventSource, () => _reclaimedConnections) + { + DisplayName = "Number of reclaimed connections from GC", + DisplayUnits = "count", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + } +#else + [PerformanceCounterPermission(SecurityAction.Assert, PermissionAccess = PerformanceCounterPermissionAccess.Write, + MachineName = ".", CategoryName = PerformanceCounterCategoryName)] + private void EnablePerformanceCounters() + { + AppDomain.CurrentDomain.DomainUnload += ExitOrUnloadEventHandler; + AppDomain.CurrentDomain.ProcessExit += ExitOrUnloadEventHandler; + AppDomain.CurrentDomain.UnhandledException += ExceptionEventHandler; + + // level 0-3: hard connects/disconnects, plus basic pool/pool entry statistics + _hardConnectsRate = CreatePerformanceCounter("HardConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _hardDisconnectsRate = CreatePerformanceCounter("HardDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _nonPooledConnections = CreatePerformanceCounter("NumberOfNonPooledConnections", PerformanceCounterType.NumberOfItems32); + _pooledConnections = CreatePerformanceCounter("NumberOfPooledConnections", PerformanceCounterType.NumberOfItems32); + _activeConnectionPoolGroups = CreatePerformanceCounter("NumberOfActiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); + _inactiveConnectionPoolGroups = CreatePerformanceCounter("NumberOfInactiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); + _activeConnectionPools = CreatePerformanceCounter("NumberOfActiveConnectionPools", PerformanceCounterType.NumberOfItems32); + _inactiveConnectionPools = CreatePerformanceCounter("NumberOfInactiveConnectionPools", PerformanceCounterType.NumberOfItems32); + _stasisConnections = CreatePerformanceCounter("NumberOfStasisConnections", PerformanceCounterType.NumberOfItems32); + _reclaimedConnections = CreatePerformanceCounter("NumberOfReclaimedConnections", PerformanceCounterType.NumberOfItems32); + + TraceSwitch perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); + if (TraceLevel.Verbose == perfCtrSwitch.Level) + { + _softConnectsRate = CreatePerformanceCounter("SoftConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _softDisconnectsRate = CreatePerformanceCounter("SoftDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _activeConnections = CreatePerformanceCounter("NumberOfActiveConnections", PerformanceCounterType.NumberOfItems32); + _freeConnections = CreatePerformanceCounter("NumberOfFreeConnections", PerformanceCounterType.NumberOfItems32); + } + } + + [PrePrepareMethod] + private void ExitOrUnloadEventHandler(object sender, EventArgs e) + { + RemovePerformanceCounters(); + } + + [PrePrepareMethod] + private void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs e) + { + if (e != null && e.IsTerminating) + { + RemovePerformanceCounters(); + } + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private void RemovePerformanceCounters() + { + // ExceptionEventHandler with IsTerminating may be called before + // the Connection Close is called or the variables are initialized + _hardConnectsRate?.RemoveInstance(); + _hardDisconnectsRate?.RemoveInstance(); + _softConnectsRate?.RemoveInstance(); + _softDisconnectsRate?.RemoveInstance(); + _nonPooledConnections?.RemoveInstance(); + _pooledConnections?.RemoveInstance(); + _activeConnectionPoolGroups?.RemoveInstance(); + _inactiveConnectionPoolGroups?.RemoveInstance(); + _activeConnectionPools?.RemoveInstance(); + _inactiveConnectionPools?.RemoveInstance(); + _activeConnections?.RemoveInstance(); + _freeConnections?.RemoveInstance(); + _stasisConnections?.RemoveInstance(); + _reclaimedConnections?.RemoveInstance(); + } + + private PerformanceCounter? CreatePerformanceCounter(string counterName, PerformanceCounterType counterType) + { + PerformanceCounter? instance = null; + + _instanceName ??= GetInstanceName(); + try + { + instance = new PerformanceCounter(); + instance.CategoryName = PerformanceCounterCategoryName; + instance.CounterName = counterName; + instance.InstanceName = _instanceName; + instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process; + instance.ReadOnly = false; + instance.RawValue = 0; // make sure we start out at zero + } + catch (InvalidOperationException e) + { + ADP.TraceExceptionWithoutRethrow(e); + } + + return instance; } + + // SxS: this method uses GetCurrentProcessId to construct the instance name. + // TODO: VSDD 534795 - remove the Resource* attributes if you do not use GetCurrentProcessId after the fix + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] + private static string GetInstanceName() + { + string result; + string? instanceName = GetAssemblyName(); // instance perfcounter name + + if (string.IsNullOrEmpty(instanceName)) + { + instanceName = AppDomain.CurrentDomain?.FriendlyName; + } + + // TODO: If you do not use GetCurrentProcessId after fixing VSDD 534795, please remove Resource* attributes from this method + int pid = Kernel32Safe.GetCurrentProcessId(); + + // SQLBUDT #366157 -there are several characters which have special meaning + // to PERFMON. They recommend that we translate them as shown below, to + // prevent problems. + + result = string.Format(null, "{0}[{1}]", instanceName, pid); + result = result.Replace('(', '[').Replace(')', ']').Replace('#', '_').Replace('/', '_').Replace('\\', '_'); + + // SQLBUVSTS #94625 - counter instance name cannot be greater than 127 + if (result.Length > CounterInstanceNameMaxLength) + { + // Replacing the middle part with "[...]" + // For example: if path is c:\long_path\very_(Ax200)_long__path\perftest.exe and process ID is 1234 than the resulted instance name will be: + // c:\long_path\very_(AxM)[...](AxN)_long__path\perftest.exe[1234] + // while M and N are adjusted to make each part before and after the [...] = 61 (making the total = 61 + 5 + 61 = 127) + const string insertString = "[...]"; + int firstPartLength = (CounterInstanceNameMaxLength - insertString.Length) / 2; + int lastPartLength = CounterInstanceNameMaxLength - firstPartLength - insertString.Length; + result = string.Format(null, "{0}{1}{2}", + result.Substring(0, firstPartLength), + insertString, + result.Substring(result.Length - lastPartLength, lastPartLength)); + + Debug.Assert(result.Length == CounterInstanceNameMaxLength, + string.Format(null, "wrong calculation of the instance name: expected {0}, actual: {1}", CounterInstanceNameMaxLength, result.Length)); + } + + return result; + } + + [FileIOPermission(SecurityAction.Assert, Unrestricted = true)] + private static string? GetAssemblyName() + { + // First try GetEntryAssembly name, then AppDomain.FriendlyName. + Assembly? assembly = Assembly.GetEntryAssembly(); + AssemblyName? name = assembly?.GetName(); + + return name?.Name; + } +#endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs deleted file mode 100644 index 78ad032b38..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netcore.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics.Tracing; - -#nullable enable - -namespace Microsoft.Data.SqlClient.Diagnostics -{ - internal sealed partial class SqlClientMetrics - { - private PollingCounter? _activeHardConnections; - private IncrementingPollingCounter? _hardConnectsPerSecond; - private IncrementingPollingCounter? _hardDisconnectsPerSecond; - - private PollingCounter? _activeSoftConnections; - private IncrementingPollingCounter? _softConnects; - private IncrementingPollingCounter? _softDisconnects; - - private PollingCounter? _numberOfNonPooledConnections; - private PollingCounter? _numberOfPooledConnections; - - private PollingCounter? _numberOfActiveConnectionPoolGroups; - private PollingCounter? _numberOfInactiveConnectionPoolGroups; - - private PollingCounter? _numberOfActiveConnectionPools; - private PollingCounter? _numberOfInactiveConnectionPools; - - private PollingCounter? _numberOfActiveConnections; - private PollingCounter? _numberOfFreeConnections; - private PollingCounter? _numberOfStasisConnections; - private IncrementingPollingCounter? _numberOfReclaimedConnections; - - private long _activeHardConnectionsCounter = 0; - private long _hardConnectsCounter = 0; - private long _hardDisconnectsCounter = 0; - - private long _activeSoftConnectionsCounter = 0; - private long _softConnectsCounter = 0; - private long _softDisconnectsCounter = 0; - - private long _nonPooledConnectionsCounter = 0; - private long _pooledConnectionsCounter = 0; - - private long _activeConnectionPoolGroupsCounter = 0; - private long _inactiveConnectionPoolGroupsCounter = 0; - - private long _activeConnectionPoolsCounter = 0; - private long _inactiveConnectionPoolsCounter = 0; - - private long _activeConnectionsCounter = 0; - private long _freeConnectionsCounter = 0; - private long _stasisConnectionsCounter = 0; - private long _reclaimedConnectionsCounter = 0; - - private void EnableEventCounters() - { - _activeHardConnections = _activeHardConnections ?? - new PollingCounter("active-hard-connections", _eventSource, () => _activeHardConnectionsCounter) - { - DisplayName = "Actual active connections currently made to servers", - DisplayUnits = "count" - }; - - _hardConnectsPerSecond = _hardConnectsPerSecond ?? - new IncrementingPollingCounter("hard-connects", _eventSource, () => _hardConnectsCounter) - { - DisplayName = "Actual connection rate to servers", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _hardDisconnectsPerSecond = _hardDisconnectsPerSecond ?? - new IncrementingPollingCounter("hard-disconnects", _eventSource, () => _hardDisconnectsCounter) - { - DisplayName = "Actual disconnection rate from servers", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _activeSoftConnections = _activeSoftConnections ?? - new PollingCounter("active-soft-connects", _eventSource, () => _activeSoftConnectionsCounter) - { - DisplayName = "Active connections retrieved from the connection pool", - DisplayUnits = "count" - }; - - _softConnects = _softConnects ?? - new IncrementingPollingCounter("soft-connects", _eventSource, () => _softConnectsCounter) - { - DisplayName = "Rate of connections retrieved from the connection pool", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _softDisconnects = _softDisconnects ?? - new IncrementingPollingCounter("soft-disconnects", _eventSource, () => _softDisconnectsCounter) - { - DisplayName = "Rate of connections returned to the connection pool", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _numberOfNonPooledConnections = _numberOfNonPooledConnections ?? - new PollingCounter("number-of-non-pooled-connections", _eventSource, () => _nonPooledConnectionsCounter) - { - DisplayName = "Number of connections not using connection pooling", - DisplayUnits = "count" - }; - - _numberOfPooledConnections = _numberOfPooledConnections ?? - new PollingCounter("number-of-pooled-connections", _eventSource, () => _pooledConnectionsCounter) - { - DisplayName = "Number of connections managed by the connection pool", - DisplayUnits = "count" - }; - - _numberOfActiveConnectionPoolGroups = _numberOfActiveConnectionPoolGroups ?? - new PollingCounter("number-of-active-connection-pool-groups", _eventSource, () => _activeConnectionPoolGroupsCounter) - { - DisplayName = "Number of active unique connection strings", - DisplayUnits = "count" - }; - - _numberOfInactiveConnectionPoolGroups = _numberOfInactiveConnectionPoolGroups ?? - new PollingCounter("number-of-inactive-connection-pool-groups", _eventSource, () => _inactiveConnectionPoolGroupsCounter) - { - DisplayName = "Number of unique connection strings waiting for pruning", - DisplayUnits = "count" - }; - - _numberOfActiveConnectionPools = _numberOfActiveConnectionPools ?? - new PollingCounter("number-of-active-connection-pools", _eventSource, () => _activeConnectionPoolsCounter) - { - DisplayName = "Number of active connection pools", - DisplayUnits = "count" - }; - - _numberOfInactiveConnectionPools = _numberOfInactiveConnectionPools ?? - new PollingCounter("number-of-inactive-connection-pools", _eventSource, () => _inactiveConnectionPoolsCounter) - { - DisplayName = "Number of inactive connection pools", - DisplayUnits = "count" - }; - - _numberOfActiveConnections = _numberOfActiveConnections ?? - new PollingCounter("number-of-active-connections", _eventSource, () => _activeConnectionsCounter) - { - DisplayName = "Number of active connections", - DisplayUnits = "count" - }; - - _numberOfFreeConnections = _numberOfFreeConnections ?? - new PollingCounter("number-of-free-connections", _eventSource, () => _freeConnectionsCounter) - { - DisplayName = "Number of ready connections in the connection pool", - DisplayUnits = "count" - }; - - _numberOfStasisConnections = _numberOfStasisConnections ?? - new PollingCounter("number-of-stasis-connections", _eventSource, () => _stasisConnectionsCounter) - { - DisplayName = "Number of connections currently waiting to be ready", - DisplayUnits = "count" - }; - - _numberOfReclaimedConnections = _numberOfReclaimedConnections ?? - new IncrementingPollingCounter("number-of-reclaimed-connections", _eventSource, () => _reclaimedConnectionsCounter) - { - DisplayName = "Number of reclaimed connections from GC", - DisplayUnits = "count", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs deleted file mode 100644 index cd2e42731b..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.netfx.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.ConstrainedExecution; -using System.Runtime.Versioning; -using System.Security.Permissions; -using Interop.Windows.Kernel32; -using Microsoft.Data.Common; - -#nullable enable - -namespace Microsoft.Data.SqlClient.Diagnostics -{ - [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] - internal sealed partial class SqlClientMetrics - { - private const string PerformanceCounterCategoryName = ".NET Data Provider for SqlServer"; - private const string PerformanceCounterCategoryHelp = "Counters for Microsoft.Data.SqlClient"; - private const int CounterInstanceNameMaxLength = 127; - - private PerformanceCounter? _hardConnectsPerSecond; - private PerformanceCounter? _hardDisconnectsPerSecond; - private PerformanceCounter? _softConnectsPerSecond; - private PerformanceCounter? _softDisconnectsPerSecond; - private PerformanceCounter? _numberOfNonPooledConnections; - private PerformanceCounter? _numberOfPooledConnections; - private PerformanceCounter? _numberOfActiveConnectionPoolGroups; - private PerformanceCounter? _numberOfInactiveConnectionPoolGroups; - private PerformanceCounter? _numberOfActiveConnectionPools; - private PerformanceCounter? _numberOfInactiveConnectionPools; - private PerformanceCounter? _numberOfActiveConnections; - private PerformanceCounter? _numberOfFreeConnections; - private PerformanceCounter? _numberOfStasisConnections; - private PerformanceCounter? _numberOfReclaimedConnections; - - private static PerformanceCounter? CreatePerformanceCounter(string categoryName, string instanceName, string counterName, PerformanceCounterType counterType) - { - PerformanceCounter? instance = null; - - try - { - instance = new PerformanceCounter(); - instance.CategoryName = categoryName; - instance.CounterName = counterName; - instance.InstanceName = instanceName; - instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process; - instance.ReadOnly = false; - instance.RawValue = 0; // make sure we start out at zero - } - catch (InvalidOperationException e) - { - ADP.TraceExceptionWithoutRethrow(e); - } - - return instance; - } - - [FileIOPermission(SecurityAction.Assert, Unrestricted = true)] - private static string? GetAssemblyName() - { - // First try GetEntryAssembly name, then AppDomain.FriendlyName. - Assembly? assembly = Assembly.GetEntryAssembly(); - AssemblyName? name = assembly?.GetName(); - - return name?.Name; - } - - // SxS: this method uses GetCurrentProcessId to construct the instance name. - // TODO: VSDD 534795 - remove the Resource* attributes if you do not use GetCurrentProcessId after the fix - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] - private static string GetInstanceName() - { - string result; - string? instanceName = GetAssemblyName(); // instance perfcounter name - - if (string.IsNullOrEmpty(instanceName)) - { - instanceName = AppDomain.CurrentDomain?.FriendlyName; - } - - // TODO: If you do not use GetCurrentProcessId after fixing VSDD 534795, please remove Resource* attributes from this method - int pid = Kernel32Safe.GetCurrentProcessId(); - - // SQLBUDT #366157 -there are several characters which have special meaning - // to PERFMON. They recommend that we translate them as shown below, to - // prevent problems. - - result = string.Format(null, "{0}[{1}]", instanceName, pid); - result = result.Replace('(', '[').Replace(')', ']').Replace('#', '_').Replace('/', '_').Replace('\\', '_'); - - // SQLBUVSTS #94625 - counter instance name cannot be greater than 127 - if (result.Length > CounterInstanceNameMaxLength) - { - // Replacing the middle part with "[...]" - // For example: if path is c:\long_path\very_(Ax200)_long__path\perftest.exe and process ID is 1234 than the resulted instance name will be: - // c:\long_path\very_(AxM)[...](AxN)_long__path\perftest.exe[1234] - // while M and N are adjusted to make each part before and after the [...] = 61 (making the total = 61 + 5 + 61 = 127) - const string insertString = "[...]"; - int firstPartLength = (CounterInstanceNameMaxLength - insertString.Length) / 2; - int lastPartLength = CounterInstanceNameMaxLength - firstPartLength - insertString.Length; - result = string.Format(null, "{0}{1}{2}", - result.Substring(0, firstPartLength), - insertString, - result.Substring(result.Length - lastPartLength, lastPartLength)); - - Debug.Assert(result.Length == CounterInstanceNameMaxLength, - string.Format(null, "wrong calculation of the instance name: expected {0}, actual: {1}", CounterInstanceNameMaxLength, result.Length)); - } - - return result; - } - - [PerformanceCounterPermission(SecurityAction.Assert, PermissionAccess = PerformanceCounterPermissionAccess.Write, - MachineName = ".", CategoryName = PerformanceCounterCategoryName)] - private void EnablePerformanceCounters() - { - AppDomain.CurrentDomain.DomainUnload += UnloadEventHandler; - AppDomain.CurrentDomain.ProcessExit += ExitEventHandler; - AppDomain.CurrentDomain.UnhandledException += ExceptionEventHandler; - - string instanceName = GetInstanceName(); - - // level 0-3: hard connects/disconnects, plus basic pool/pool entry statistics - - _hardConnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "HardConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); - _hardDisconnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "HardDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); - _numberOfNonPooledConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfNonPooledConnections", PerformanceCounterType.NumberOfItems32); - _numberOfPooledConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfPooledConnections", PerformanceCounterType.NumberOfItems32); - _numberOfActiveConnectionPoolGroups = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfActiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); - _numberOfInactiveConnectionPoolGroups = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfInactiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); - _numberOfActiveConnectionPools = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfActiveConnectionPools", PerformanceCounterType.NumberOfItems32); - _numberOfInactiveConnectionPools = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfInactiveConnectionPools", PerformanceCounterType.NumberOfItems32); - _numberOfStasisConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfStasisConnections", PerformanceCounterType.NumberOfItems32); - _numberOfReclaimedConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfReclaimedConnections", PerformanceCounterType.NumberOfItems32); - - TraceSwitch perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); - if (TraceLevel.Verbose == perfCtrSwitch.Level) - { - _softConnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "SoftConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); - _softDisconnectsPerSecond = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "SoftDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); - _numberOfActiveConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfActiveConnections", PerformanceCounterType.NumberOfItems32); - _numberOfFreeConnections = CreatePerformanceCounter(PerformanceCounterCategoryName, instanceName, "NumberOfFreeConnections", PerformanceCounterType.NumberOfItems32); - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - private void RemovePerformanceCounters() - { - // ExceptionEventHandler with IsTerminating may be called before - // the Connection Close is called or the variables are initialized - _hardConnectsPerSecond?.RemoveInstance(); - _hardDisconnectsPerSecond?.RemoveInstance(); - _softConnectsPerSecond?.RemoveInstance(); - _softDisconnectsPerSecond?.RemoveInstance(); - _numberOfNonPooledConnections?.RemoveInstance(); - _numberOfPooledConnections?.RemoveInstance(); - _numberOfActiveConnectionPoolGroups?.RemoveInstance(); - _numberOfInactiveConnectionPoolGroups?.RemoveInstance(); - _numberOfActiveConnectionPools?.RemoveInstance(); - _numberOfInactiveConnectionPools?.RemoveInstance(); - _numberOfActiveConnections?.RemoveInstance(); - _numberOfFreeConnections?.RemoveInstance(); - _numberOfStasisConnections?.RemoveInstance(); - _numberOfReclaimedConnections?.RemoveInstance(); - } - - [PrePrepareMethod] - private void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs e) - { - if (e != null && e.IsTerminating) - { - RemovePerformanceCounters(); - } - } - - [PrePrepareMethod] - private void ExitEventHandler(object sender, EventArgs e) - { - RemovePerformanceCounters(); - } - - [PrePrepareMethod] - private void UnloadEventHandler(object sender, EventArgs e) - { - RemovePerformanceCounters(); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs index 6a8d503e53..90a69b5670 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs @@ -26,15 +26,17 @@ private SqlClientEventSource() { } private const string NullStr = "null"; private const string SqlCommand_ClassName = nameof(SqlCommand); +#if NET protected override void OnEventCommand(EventCommandEventArgs command) { base.OnEventCommand(command); if (command.Command == EventCommand.Enable) { - Metrics.EnableTraceMetrics(); + Metrics.EnableEventCounters(); } } +#endif #region Event IDs // Initialized static Scope IDs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs index 723be21bd5..ded0dee6cc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs @@ -348,24 +348,31 @@ static SqlClientEventSourceProps() { s_log = SqlClientEventSource.Metrics; -#if NET - s_getActiveHardConnections = GenerateFieldGetter("_activeHardConnectionsCounter"); - s_getHardConnects = GenerateFieldGetter("_hardConnectsCounter"); - s_getHardDisconnects = GenerateFieldGetter("_hardDisconnectsCounter"); - s_getActiveSoftConnections = GenerateFieldGetter("_activeSoftConnectionsCounter"); - s_getSoftConnects = GenerateFieldGetter("_softConnectsCounter"); - s_getSoftDisconnects = GenerateFieldGetter("_softDisconnectsCounter"); - s_getNonPooledConnections = GenerateFieldGetter("_nonPooledConnectionsCounter"); - s_getPooledConnections = GenerateFieldGetter("_pooledConnectionsCounter"); - s_getActiveConnectionPoolGroups = GenerateFieldGetter("_activeConnectionPoolGroupsCounter"); - s_getInactiveConnectionPoolGroups = GenerateFieldGetter("_inactiveConnectionPoolGroupsCounter"); - s_getActiveConnectionPools = GenerateFieldGetter("_activeConnectionPoolsCounter"); - s_getInactiveConnectionPools = GenerateFieldGetter("_inactiveConnectionPoolsCounter"); - s_getActiveConnections = GenerateFieldGetter("_activeConnectionsCounter"); - s_getFreeConnections = GenerateFieldGetter("_freeConnectionsCounter"); - s_getStasisConnections = GenerateFieldGetter("_stasisConnectionsCounter"); - s_getReclaimedConnections = GenerateFieldGetter("_reclaimedConnectionsCounter"); +#if NETFRAMEWORK + Func notApplicableFunction = static () => -1; + + // .NET Framework doesn't have performance counters for the number of hard and soft connections. + s_getActiveHardConnections = notApplicableFunction; + s_getActiveSoftConnections = notApplicableFunction; +#endif + s_getActiveHardConnections = GenerateFieldGetter("_activeHardConnections"); + s_getHardConnects = GenerateFieldGetter("_hardConnectsRate"); + s_getHardDisconnects = GenerateFieldGetter("_hardDisconnectsRate"); + s_getActiveSoftConnections = GenerateFieldGetter("_activeSoftConnections"); + s_getSoftConnects = GenerateFieldGetter("_softConnectsRate"); + s_getSoftDisconnects = GenerateFieldGetter("_softDisconnectsRate"); + s_getNonPooledConnections = GenerateFieldGetter("_nonPooledConnections"); + s_getPooledConnections = GenerateFieldGetter("_pooledConnections"); + s_getActiveConnectionPoolGroups = GenerateFieldGetter("_activeConnectionPoolGroups"); + s_getInactiveConnectionPoolGroups = GenerateFieldGetter("_inactiveConnectionPoolGroups"); + s_getActiveConnectionPools = GenerateFieldGetter("_activeConnectionPools"); + s_getInactiveConnectionPools = GenerateFieldGetter("_inactiveConnectionPools"); + s_getActiveConnections = GenerateFieldGetter("_activeConnections"); + s_getFreeConnections = GenerateFieldGetter("_freeConnections"); + s_getStasisConnections = GenerateFieldGetter("_stasisConnections"); + s_getReclaimedConnections = GenerateFieldGetter("_reclaimedConnections"); +#if NET static Func GenerateFieldGetter(string fieldName) { FieldInfo counterField = s_log.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); @@ -374,28 +381,7 @@ static Func GenerateFieldGetter(string fieldName) return () => (long)counterField.GetValue(s_log)!; } #else - Func notApplicableFunction = static () => -1; - - // .NET Framework doesn't have performance counters for the number of hard and soft connections. - s_getActiveHardConnections = notApplicableFunction; - s_getHardConnects = GeneratePerformanceCounterGetter("_hardConnectsPerSecond"); - s_getHardDisconnects = GeneratePerformanceCounterGetter("_hardDisconnectsPerSecond"); - s_getActiveSoftConnections = notApplicableFunction; - s_getSoftConnects = GeneratePerformanceCounterGetter("_softConnectsPerSecond"); - s_getSoftDisconnects = GeneratePerformanceCounterGetter("_softDisconnectsPerSecond"); - - s_getNonPooledConnections = GeneratePerformanceCounterGetter("_numberOfNonPooledConnections"); - s_getPooledConnections = GeneratePerformanceCounterGetter("_numberOfPooledConnections"); - s_getActiveConnectionPoolGroups = GeneratePerformanceCounterGetter("_numberOfActiveConnectionPoolGroups"); - s_getInactiveConnectionPoolGroups = GeneratePerformanceCounterGetter("_numberOfInactiveConnectionPoolGroups"); - s_getActiveConnectionPools = GeneratePerformanceCounterGetter("_numberOfActiveConnectionPools"); - s_getInactiveConnectionPools = GeneratePerformanceCounterGetter("_numberOfInactiveConnectionPools"); - s_getActiveConnections = GeneratePerformanceCounterGetter("_numberOfActiveConnections"); - s_getFreeConnections = GeneratePerformanceCounterGetter("_numberOfFreeConnections"); - s_getStasisConnections = GeneratePerformanceCounterGetter("_numberOfStasisConnections"); - s_getReclaimedConnections = GeneratePerformanceCounterGetter("_numberOfReclaimedConnections"); - - static Func GeneratePerformanceCounterGetter(string fieldName) + static Func GenerateFieldGetter(string fieldName) { FieldInfo counterField = s_log.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); Debug.Assert(counterField != null); From 845903a288fc918b269a8f1e2612d55331c85a55 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 11 Apr 2025 20:07:18 +0100 Subject: [PATCH 5/5] Account for the removal of InternalsVisibleTo --- .../tests/ManualTests/TracingTests/MetricsTest.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs index ded0dee6cc..4e90bbf6c7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs @@ -346,7 +346,13 @@ internal static class SqlClientEventSourceProps static SqlClientEventSourceProps() { - s_log = SqlClientEventSource.Metrics; + Type sqlClientEventSourceType = + Assembly.GetAssembly(typeof(SqlConnection))!.GetType("Microsoft.Data.SqlClient.SqlClientEventSource"); + Debug.Assert(sqlClientEventSourceType != null); + FieldInfo metricsField = sqlClientEventSourceType.GetField("Metrics", BindingFlags.Static | BindingFlags.Public); + Debug.Assert(metricsField != null); + Type sqlClientMetricsType = metricsField.FieldType; + s_log = metricsField.GetValue(null); #if NETFRAMEWORK Func notApplicableFunction = static () => -1;