11using System . Collections . Concurrent ;
22using System . Diagnostics ;
3+ using System . Diagnostics . Metrics ;
34using System . Net ;
45using System . Security . Authentication ;
56using Microsoft . Extensions . Logging ;
@@ -50,12 +51,14 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
5051 {
5152 if ( m_sessions . Count > 0 )
5253 {
54+ // NOTE: MetricsReporter updated outside lock below
5355 session = m_sessions . First ! . Value ;
5456 m_sessions . RemoveFirst ( ) ;
5557 }
5658 }
5759 if ( session is not null )
5860 {
61+ MetricsReporter . RemoveIdle ( this ) ;
5962 Log . FoundExistingSession ( m_logger , Id ) ;
6063 bool reuseSession ;
6164
@@ -96,8 +99,12 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
9699 m_leasedSessions . Add ( session . Id , session ) ;
97100 leasedSessionsCountPooled = m_leasedSessions . Count ;
98101 }
102+ MetricsReporter . AddUsed ( this ) ;
99103 ActivitySourceHelper . CopyTags ( session . ActivityTags , activity ) ;
100104 Log . ReturningPooledSession ( m_logger , Id , session . Id , leasedSessionsCountPooled ) ;
105+
106+ session . LastLeasedTicks = unchecked ( ( uint ) Environment . TickCount ) ;
107+ MetricsReporter . RecordWaitTime ( this , unchecked ( session . LastLeasedTicks - ( uint ) startTickCount ) ) ;
101108 return session ;
102109 }
103110 }
@@ -112,7 +119,11 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
112119 m_leasedSessions . Add ( session . Id , session ) ;
113120 leasedSessionsCountNew = m_leasedSessions . Count ;
114121 }
122+ MetricsReporter . AddUsed ( this ) ;
115123 Log . ReturningNewSession ( m_logger , Id , session . Id , leasedSessionsCountNew ) ;
124+
125+ session . LastLeasedTicks = unchecked ( ( uint ) Environment . TickCount ) ;
126+ MetricsReporter . RecordCreateTime ( this , unchecked ( session . LastLeasedTicks - ( uint ) startTickCount ) ) ;
116127 return session ;
117128 }
118129 catch ( Exception ex )
@@ -164,12 +175,14 @@ public async ValueTask ReturnAsync(IOBehavior ioBehavior, ServerSession session)
164175 {
165176 lock ( m_leasedSessions )
166177 m_leasedSessions . Remove ( session . Id ) ;
178+ MetricsReporter . RemoveUsed ( this ) ;
167179 session . OwningConnection = null ;
168180 var sessionHealth = GetSessionHealth ( session ) ;
169181 if ( sessionHealth == 0 )
170182 {
171183 lock ( m_sessions )
172184 m_sessions . AddFirst ( session ) ;
185+ MetricsReporter . AddIdle ( this ) ;
173186 }
174187 else
175188 {
@@ -224,6 +237,8 @@ public async Task ReapAsync(IOBehavior ioBehavior, CancellationToken cancellatio
224237 public void Dispose ( )
225238 {
226239 Log . DisposingConnectionPool ( m_logger , Id ) ;
240+ lock ( s_allPools )
241+ s_allPools . Remove ( this ) ;
227242#if NET6_0_OR_GREATER
228243 m_dnsCheckTimer ? . Dispose ( ) ;
229244 m_dnsCheckTimer = null ;
@@ -326,12 +341,14 @@ private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<ServerSession, boo
326341 {
327342 if ( m_sessions . Count > 0 )
328343 {
344+ // NOTE: MetricsReporter updated outside lock below
329345 session = m_sessions . Last ! . Value ;
330346 m_sessions . RemoveLast ( ) ;
331347 }
332348 }
333349 if ( session is null )
334350 return ;
351+ MetricsReporter . RemoveIdle ( this ) ;
335352
336353 if ( shouldCleanFn ( session ) )
337354 {
@@ -344,6 +361,7 @@ private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<ServerSession, boo
344361 // session should not be cleaned; put it back in the queue and stop iterating
345362 lock ( m_sessions )
346363 m_sessions . AddLast ( session ) ;
364+ MetricsReporter . AddIdle ( this ) ;
347365 return ;
348366 }
349367 }
@@ -389,6 +407,7 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh
389407 AdjustHostConnectionCount ( session , 1 ) ;
390408 lock ( m_sessions )
391409 m_sessions . AddFirst ( session ) ;
410+ MetricsReporter . AddIdle ( this ) ;
392411 }
393412 finally
394413 {
@@ -546,17 +565,18 @@ private async ValueTask<ServerSession> ConnectSessionAsync(MySqlConnection conne
546565 else if ( pool != newPool )
547566 {
548567 Log . CreatedPoolWillNotBeUsed ( newPool . m_logger , newPool . Id ) ;
568+ newPool . Dispose ( ) ;
549569 }
550570
551571 return pool ;
552572 }
553573
554574 public static async Task ClearPoolsAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
555575 {
556- foreach ( var pool in GetAllPools ( ) )
576+ foreach ( var pool in GetCachedPools ( ) )
557577 await pool . ClearAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
558578
559- static List < ConnectionPool > GetAllPools ( )
579+ static List < ConnectionPool > GetCachedPools ( )
560580 {
561581 var pools = new List < ConnectionPool > ( s_pools . Count ) ;
562582 var uniquePools = new HashSet < ConnectionPool > ( ) ;
@@ -594,8 +614,19 @@ private ConnectionPool(MySqlConnectorLoggingConfiguration loggingConfiguration,
594614 cs . LoadBalance == MySqlLoadBalance . LeastConnections ? new LeastConnectionsLoadBalancer ( m_hostSessions ! ) :
595615 ( ILoadBalancer ) new RoundRobinLoadBalancer ( ) ;
596616
617+ // create tag lists for reporting pool metrics
618+ var connectionString = cs . ConnectionStringBuilder . GetConnectionString ( includePassword : false ) ;
619+ m_stateTagList =
620+ [
621+ new ( "state" , "idle" ) ,
622+ new ( "pool.name" , Name ?? connectionString ) ,
623+ new ( "state" , "used" ) ,
624+ ] ;
625+
597626 Id = Interlocked . Increment ( ref s_poolId ) ;
598- Log . CreatingNewConnectionPool ( m_logger , Id , cs . ConnectionStringBuilder . GetConnectionString ( includePassword : false ) ) ;
627+ lock ( s_allPools )
628+ s_allPools . Add ( this ) ;
629+ Log . CreatingNewConnectionPool ( m_logger , Id , connectionString ) ;
599630 }
600631
601632 private void StartReaperTask ( )
@@ -741,6 +772,19 @@ private void AdjustHostConnectionCount(ServerSession session, int delta)
741772 }
742773 }
743774
775+ // Provides a slice of m_stateTagList that contains either the 'idle' or 'used' state tag along with the pool name.
776+ public ReadOnlySpan < KeyValuePair < string , object ? > > IdleStateTagList => m_stateTagList . AsSpan ( 0 , 2 ) ;
777+ public ReadOnlySpan < KeyValuePair < string , object ? > > UsedStateTagList => m_stateTagList . AsSpan ( 1 , 2 ) ;
778+
779+ // A slice of m_stateTagList that contains only the pool name tag.
780+ public ReadOnlySpan < KeyValuePair < string , object ? > > PoolNameTagList => m_stateTagList . AsSpan ( 1 , 1 ) ;
781+
782+ public static List < ConnectionPool > GetAllPools ( )
783+ {
784+ lock ( s_allPools )
785+ return new ( s_allPools ) ;
786+ }
787+
744788 private sealed class LeastConnectionsLoadBalancer ( Dictionary < string , int > hostSessions ) : ILoadBalancer
745789 {
746790 public IReadOnlyList < string > LoadBalance ( IReadOnlyList < string > hosts )
@@ -766,6 +810,7 @@ private static void OnAppDomainShutDown(object? sender, EventArgs e) =>
766810 ClearPoolsAsync ( IOBehavior . Synchronous , CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
767811
768812 private static readonly ConcurrentDictionary < string , ConnectionPool ? > s_pools = new ( ) ;
813+ private static readonly List < ConnectionPool > s_allPools = new ( ) ;
769814 private static readonly Action < ILogger , int , string , Exception ? > s_createdNewSession = LoggerMessage . Define < int , string > (
770815 LogLevel . Debug , new EventId ( EventIds . PoolCreatedNewSession , nameof ( EventIds . PoolCreatedNewSession ) ) ,
771816 "Pool {PoolId} has no pooled session available; created new session {SessionId}" ) ;
@@ -777,6 +822,7 @@ private static void OnAppDomainShutDown(object? sender, EventArgs e) =>
777822
778823 private readonly ILogger m_logger ;
779824 private readonly ILogger m_connectionLogger ;
825+ private readonly KeyValuePair < string , object ? > [ ] m_stateTagList ;
780826 private readonly SemaphoreSlim m_cleanSemaphore ;
781827 private readonly SemaphoreSlim m_sessionSemaphore ;
782828 private readonly LinkedList < ServerSession > m_sessions ;
0 commit comments