1
1
using System . Collections . Concurrent ;
2
2
using System . Diagnostics ;
3
+ using System . Diagnostics . Metrics ;
3
4
using System . Net ;
4
5
using System . Security . Authentication ;
5
6
using Microsoft . Extensions . Logging ;
@@ -19,6 +20,8 @@ internal sealed class ConnectionPool : IDisposable
19
20
20
21
public SslProtocols SslProtocols { get ; set ; }
21
22
23
+ public void AddPendingRequestCount ( int delta ) => s_pendingRequestsCounter . Add ( delta , PoolNameTagList ) ;
24
+
22
25
public async ValueTask < ServerSession > GetSessionAsync ( MySqlConnection connection , int startTickCount , Activity ? activity , IOBehavior ioBehavior , CancellationToken cancellationToken )
23
26
{
24
27
cancellationToken . ThrowIfCancellationRequested ( ) ;
@@ -50,12 +53,14 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
50
53
{
51
54
if ( m_sessions . Count > 0 )
52
55
{
56
+ // NOTE: s_connectionsUsageCounter updated outside lock below
53
57
session = m_sessions . First ! . Value ;
54
58
m_sessions . RemoveFirst ( ) ;
55
59
}
56
60
}
57
61
if ( session is not null )
58
62
{
63
+ s_connectionsUsageCounter . Add ( - 1 , IdleStateTagList ) ;
59
64
Log . FoundExistingSession ( m_logger , Id ) ;
60
65
bool reuseSession ;
61
66
@@ -89,8 +94,12 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
89
94
m_leasedSessions . Add ( session . Id , session ) ;
90
95
leasedSessionsCountPooled = m_leasedSessions . Count ;
91
96
}
97
+ s_connectionsUsageCounter . Add ( 1 , UsedStateTagList ) ;
92
98
ActivitySourceHelper . CopyTags ( session . ActivityTags , activity ) ;
93
99
Log . ReturningPooledSession ( m_logger , Id , session . Id , leasedSessionsCountPooled ) ;
100
+
101
+ session . LastLeasedTicks = unchecked ( ( uint ) Environment . TickCount ) ;
102
+ s_waitTimeHistory . Record ( unchecked ( session . LastLeasedTicks - ( uint ) startTickCount ) , PoolNameTagList ) ;
94
103
return session ;
95
104
}
96
105
}
@@ -105,7 +114,11 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
105
114
m_leasedSessions . Add ( session . Id , session ) ;
106
115
leasedSessionsCountNew = m_leasedSessions . Count ;
107
116
}
117
+ s_connectionsUsageCounter . Add ( 1 , UsedStateTagList ) ;
108
118
Log . ReturningNewSession ( m_logger , Id , session . Id , leasedSessionsCountNew ) ;
119
+
120
+ session . LastLeasedTicks = unchecked ( ( uint ) Environment . TickCount ) ;
121
+ s_createTimeHistory . Record ( unchecked ( session . LastLeasedTicks - ( uint ) startTickCount ) , PoolNameTagList ) ;
109
122
return session ;
110
123
}
111
124
catch ( Exception ex )
@@ -157,12 +170,14 @@ public async ValueTask ReturnAsync(IOBehavior ioBehavior, ServerSession session)
157
170
{
158
171
lock ( m_leasedSessions )
159
172
m_leasedSessions . Remove ( session . Id ) ;
173
+ s_connectionsUsageCounter . Add ( - 1 , UsedStateTagList ) ;
160
174
session . OwningConnection = null ;
161
175
var sessionHealth = GetSessionHealth ( session ) ;
162
176
if ( sessionHealth == 0 )
163
177
{
164
178
lock ( m_sessions )
165
179
m_sessions . AddFirst ( session ) ;
180
+ s_connectionsUsageCounter . Add ( 1 , IdleStateTagList ) ;
166
181
}
167
182
else
168
183
{
@@ -236,6 +251,10 @@ public void Dispose()
236
251
reaperWaitHandle . WaitOne ( ) ;
237
252
}
238
253
#endif
254
+
255
+ s_minIdleConnectionsCounter . Add ( - ConnectionSettings . MinimumPoolSize , PoolNameTagList ) ;
256
+ s_maxIdleConnectionsCounter . Add ( - ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
257
+ s_maxConnectionsCounter . Add ( - ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
239
258
}
240
259
241
260
/// <summary>
@@ -319,12 +338,14 @@ private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<ServerSession, boo
319
338
{
320
339
if ( m_sessions . Count > 0 )
321
340
{
341
+ // NOTE: s_connectionsUsageCounter updated outside lock below
322
342
session = m_sessions . Last ! . Value ;
323
343
m_sessions . RemoveLast ( ) ;
324
344
}
325
345
}
326
346
if ( session is null )
327
347
return ;
348
+ s_connectionsUsageCounter . Add ( - 1 , IdleStateTagList ) ;
328
349
329
350
if ( shouldCleanFn ( session ) )
330
351
{
@@ -337,6 +358,7 @@ private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<ServerSession, boo
337
358
// session should not be cleaned; put it back in the queue and stop iterating
338
359
lock ( m_sessions )
339
360
m_sessions . AddLast ( session ) ;
361
+ s_connectionsUsageCounter . Add ( 1 , IdleStateTagList ) ;
340
362
return ;
341
363
}
342
364
}
@@ -382,6 +404,7 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh
382
404
AdjustHostConnectionCount ( session , 1 ) ;
383
405
lock ( m_sessions )
384
406
m_sessions . AddFirst ( session ) ;
407
+ s_connectionsUsageCounter . Add ( 1 , IdleStateTagList ) ;
385
408
}
386
409
finally
387
410
{
@@ -587,8 +610,22 @@ private ConnectionPool(MySqlConnectorLoggingConfiguration loggingConfiguration,
587
610
cs . LoadBalance == MySqlLoadBalance . LeastConnections ? new LeastConnectionsLoadBalancer ( m_hostSessions ! ) :
588
611
( ILoadBalancer ) new RoundRobinLoadBalancer ( ) ;
589
612
613
+ // create tag lists for reporting pool metrics
614
+ var connectionString = cs . ConnectionStringBuilder . GetConnectionString ( includePassword : false ) ;
615
+ m_stateTagList = new KeyValuePair < string , object ? > [ 3 ]
616
+ {
617
+ new ( "state" , "idle" ) ,
618
+ new ( "pool.name" , Name ?? connectionString ) ,
619
+ new ( "state" , "used" ) ,
620
+ } ;
621
+
622
+ // set pool size counters
623
+ s_minIdleConnectionsCounter . Add ( ConnectionSettings . MinimumPoolSize , PoolNameTagList ) ;
624
+ s_maxIdleConnectionsCounter . Add ( ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
625
+ s_maxConnectionsCounter . Add ( ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
626
+
590
627
Id = Interlocked . Increment ( ref s_poolId ) ;
591
- Log . CreatingNewConnectionPool ( m_logger , Id , cs . ConnectionStringBuilder . GetConnectionString ( includePassword : false ) ) ;
628
+ Log . CreatingNewConnectionPool ( m_logger , Id , connectionString ) ;
592
629
}
593
630
594
631
private void StartReaperTask ( )
@@ -734,6 +771,13 @@ private void AdjustHostConnectionCount(ServerSession session, int delta)
734
771
}
735
772
}
736
773
774
+ // Provides a slice of m_stateTagList that contains either the 'idle' or 'used' state tag along with the pool name.
775
+ private ReadOnlySpan < KeyValuePair < string , object ? > > IdleStateTagList => m_stateTagList . AsSpan ( 0 , 2 ) ;
776
+ private ReadOnlySpan < KeyValuePair < string , object ? > > UsedStateTagList => m_stateTagList . AsSpan ( 1 , 2 ) ;
777
+
778
+ // A slice of m_stateTagList that contains only the pool name tag.
779
+ public ReadOnlySpan < KeyValuePair < string , object ? > > PoolNameTagList => m_stateTagList . AsSpan ( 1 , 1 ) ;
780
+
737
781
private sealed class LeastConnectionsLoadBalancer : ILoadBalancer
738
782
{
739
783
public LeastConnectionsLoadBalancer ( Dictionary < string , int > hostSessions ) => m_hostSessions = hostSessions ;
@@ -768,6 +812,20 @@ static ConnectionPool()
768
812
private static void OnAppDomainShutDown ( object ? sender , EventArgs e ) =>
769
813
ClearPoolsAsync ( IOBehavior . Synchronous , CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
770
814
815
+ private static readonly UpDownCounter < int > s_connectionsUsageCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.usage" ,
816
+ unit : "{connection}" , description : "The number of connections that are currently in the state described by the state tag." ) ;
817
+ private static readonly UpDownCounter < int > s_maxIdleConnectionsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.idle.max" ,
818
+ unit : "{connection}" , description : "The maximum number of idle open connections allowed." ) ;
819
+ private static readonly UpDownCounter < int > s_minIdleConnectionsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.idle.min" ,
820
+ unit : "{connection}" , description : "The minimum number of idle open connections allowed." ) ;
821
+ private static readonly UpDownCounter < int > s_maxConnectionsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.max" ,
822
+ unit : "{connection}" , description : "The maximum number of open connections allowed." ) ;
823
+ private static readonly UpDownCounter < int > s_pendingRequestsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.pending_requests" ,
824
+ unit : "{request}" , description : "The number of pending requests for an open connection, cumulative for the entire pool." ) ;
825
+ private static readonly Histogram < float > s_createTimeHistory = ActivitySourceHelper . Meter . CreateHistogram < float > ( "db.client.connections.create_time" ,
826
+ unit : "ms" , description : "The time it took to create a new connection." ) ;
827
+ private static readonly Histogram < float > s_waitTimeHistory = ActivitySourceHelper . Meter . CreateHistogram < float > ( "db.client.connections.wait_time" ,
828
+ unit : "ms" , description : "The time it took to obtain an open connection from the pool." ) ;
771
829
private static readonly ConcurrentDictionary < string , ConnectionPool ? > s_pools = new ( ) ;
772
830
private static readonly Action < ILogger , int , string , Exception ? > s_createdNewSession = LoggerMessage . Define < int , string > (
773
831
LogLevel . Debug , new EventId ( EventIds . PoolCreatedNewSession , nameof ( EventIds . PoolCreatedNewSession ) ) ,
@@ -780,6 +838,7 @@ private static void OnAppDomainShutDown(object? sender, EventArgs e) =>
780
838
781
839
private readonly ILogger m_logger ;
782
840
private readonly ILogger m_connectionLogger ;
841
+ private readonly KeyValuePair < string , object ? > [ ] m_stateTagList ;
783
842
private readonly SemaphoreSlim m_cleanSemaphore ;
784
843
private readonly SemaphoreSlim m_sessionSemaphore ;
785
844
private readonly LinkedList < ServerSession > m_sessions ;
0 commit comments