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 , int timeoutMilliseconds , 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
@@ -96,8 +101,12 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
96
101
m_leasedSessions . Add ( session . Id , session ) ;
97
102
leasedSessionsCountPooled = m_leasedSessions . Count ;
98
103
}
104
+ s_connectionsUsageCounter . Add ( 1 , UsedStateTagList ) ;
99
105
ActivitySourceHelper . CopyTags ( session . ActivityTags , activity ) ;
100
106
Log . ReturningPooledSession ( m_logger , Id , session . Id , leasedSessionsCountPooled ) ;
107
+
108
+ session . LastLeasedTicks = unchecked ( ( uint ) Environment . TickCount ) ;
109
+ s_waitTimeHistory . Record ( unchecked ( session . LastLeasedTicks - ( uint ) startTickCount ) , PoolNameTagList ) ;
101
110
return session ;
102
111
}
103
112
}
@@ -112,7 +121,11 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
112
121
m_leasedSessions . Add ( session . Id , session ) ;
113
122
leasedSessionsCountNew = m_leasedSessions . Count ;
114
123
}
124
+ s_connectionsUsageCounter . Add ( 1 , UsedStateTagList ) ;
115
125
Log . ReturningNewSession ( m_logger , Id , session . Id , leasedSessionsCountNew ) ;
126
+
127
+ session . LastLeasedTicks = unchecked ( ( uint ) Environment . TickCount ) ;
128
+ s_createTimeHistory . Record ( unchecked ( session . LastLeasedTicks - ( uint ) startTickCount ) , PoolNameTagList ) ;
116
129
return session ;
117
130
}
118
131
catch ( Exception ex )
@@ -164,12 +177,14 @@ public async ValueTask ReturnAsync(IOBehavior ioBehavior, ServerSession session)
164
177
{
165
178
lock ( m_leasedSessions )
166
179
m_leasedSessions . Remove ( session . Id ) ;
180
+ s_connectionsUsageCounter . Add ( - 1 , UsedStateTagList ) ;
167
181
session . OwningConnection = null ;
168
182
var sessionHealth = GetSessionHealth ( session ) ;
169
183
if ( sessionHealth == 0 )
170
184
{
171
185
lock ( m_sessions )
172
186
m_sessions . AddFirst ( session ) ;
187
+ s_connectionsUsageCounter . Add ( 1 , IdleStateTagList ) ;
173
188
}
174
189
else
175
190
{
@@ -243,6 +258,10 @@ public void Dispose()
243
258
reaperWaitHandle . WaitOne ( ) ;
244
259
}
245
260
#endif
261
+
262
+ s_minIdleConnectionsCounter . Add ( - ConnectionSettings . MinimumPoolSize , PoolNameTagList ) ;
263
+ s_maxIdleConnectionsCounter . Add ( - ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
264
+ s_maxConnectionsCounter
10000
. Add ( - ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
246
265
}
247
266
248
267
/// <summary>
@@ -326,12 +345,14 @@ private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<ServerSession, boo
326
345
{
327
346
if ( m_sessions . Count > 0 )
328
347
{
348
+ // NOTE: s_connectionsUsageCounter updated outside lock below
329
349
session = m_sessions . Last ! . Value ;
330
350
m_sessions . RemoveLast ( ) ;
331
351
}
332
352
}
333
353
if ( session is null )
334
354
return ;
355
+ s_connectionsUsageCounter . Add ( - 1 , IdleStateTagList ) ;
335
356
336
357
if ( shouldCleanFn ( session ) )
337
358
{
@@ -344,6 +365,7 @@ private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<ServerSession, boo
344
365
// session should not be cleaned; put it back in the queue and stop iterating
345
366
lock ( m_sessions )
346
367
m_sessions . AddLast ( session ) ;
368
+ s_connectionsUsageCounter . Add ( 1 , IdleStateTagList ) ;
347
369
return ;
348
370
}
349
371
}
@@ -389,6 +411,7 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh
389
411
AdjustHostConnectionCount ( session , 1 ) ;
390
412
lock ( m_sessions )
391
413
m_sessions . AddFirst ( session ) ;
414
+ s_connectionsUsageCounter . Add ( 1 , IdleStateTagList ) ;
392
415
}
393
416
finally
394
417
{
@@ -594,8 +617,22 @@ private ConnectionPool(MySqlConnectorLoggingConfiguration loggingConfiguration,
594
617
cs . LoadBalance == MySqlLoadBalance . LeastConnections ? new LeastConnectionsLoadBalancer ( m_hostSessions ! ) :
595
618
( ILoadBalancer ) new RoundRobinLoadBalancer ( ) ;
596
619
620
+ // create tag lists for reporting pool metrics
621
+ var connectionString = cs . ConnectionStringBuilder . GetConnectionString ( includePassword : false ) ;
622
+ m_stateTagList =
623
+ [
624
+ new ( "state" , "idle" ) ,
625
+ new ( "pool.name" , Name ?? connectionString ) ,
626
+ new ( "state" , "used" ) ,
627
+ ] ;
628
+
629
+ // set pool size counters
630
+ s_minIdleConnectionsCounter . Add ( ConnectionSettings . MinimumPoolSize , PoolNameTagList ) ;
631
+ s_maxIdleConnectionsCounter . Add ( ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
632
+ s_maxConnectionsCounter . Add ( ConnectionSettings . MaximumPoolSize , PoolNameTagList ) ;
633
+
597
634
Id = Interlocked . Increment ( ref s_poolId ) ;
598
- Log . CreatingNewConnectionPool ( m_logger , Id , cs . ConnectionStringBuilder . GetConnectionString ( includePassword : false ) ) ;
635
+ Log . CreatingNewConnectionPool ( m_logger , Id , connectionString ) ;
599
636
}
600
637
601
638
private void StartReaperTask ( )
@@ -741,6 +778,13 @@ private void AdjustHostConnectionCount(ServerSession session, int delta)
741
778
}
742
779
}
743
780
781
+ // Provides a slice of m_stateTagList that contains either the 'idle' or 'used' state tag along with the pool name.
782
+ private ReadOnlySpan < KeyValuePair < string , object ? > > IdleStateTagList => m_stateTagList . AsSpan ( 0 , 2 ) ;
783
+ private ReadOnlySpan < KeyValuePair < string , object ? > > UsedStateTagList => m_stateTagList . AsSpan ( 1 , 2 ) ;
784
+
785
+ // A slice of m_stateTagList that contains only the pool name tag.
786
+ public ReadOnlySpan < KeyValuePair < string , object ? > > PoolNameTagList => m_stateTagList . AsSpan ( 1 , 1 ) ;
787
+
744
788
private sealed class LeastConnectionsLoadBalancer : ILoadBalancer
745
789
{
746
790
public LeastConnectionsLoadBalancer ( Dictionary < string , int > hostSessions ) => m_hostSessions = hostSessions ;
@@ -775,6 +819,20 @@ static ConnectionPool()
775
819
private static void OnAppDomainShutDown ( object ? sender , EventArgs e ) =>
776
820
ClearPoolsAsync ( IOBehavior . Synchronous , CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ;
777
821
822
+ private static readonly UpDownCounter < int > s_connectionsUsageCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.usage" ,
823
+ unit : "{connection}" , description : "The number of connections that are currently in the state described by the state tag." ) ;
824
+ private static readonly UpDownCounter < int > s_maxIdleConnectionsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.idle.max" ,
825
+ unit : "{connection}" , description : "The maximum number of idle open connections allowed." ) ;
826
+ private static readonly UpDownCounter < int > s_minIdleConnectionsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.idle.min" ,
827
+ unit : "{connection}" , description : "The minimum number of idle open connections allowed." ) ;
828
+ private static readonly UpDownCounter < int > s_maxConnectionsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.max" ,
829
+ unit : "{connection}" , description : "The maximum number of open connections allowed." ) ;
830
+ private static readonly UpDownCounter < int > s_pendingRequestsCounter = ActivitySourceHelper . Meter . CreateUpDownCounter < int > ( "db.client.connections.pending_requests" ,
831
+ unit : "{request}" , description : "The number of pending requests for an open connection, cumulative for the entire pool." ) ;
832
+ private static readonly Histogram < float > s_createTimeHistory = ActivitySourceHelper . Meter . CreateHistogram < float > ( "db.client.connections.create_time" ,
833
+ unit : "ms" , description : "The time it took to create a new connection." ) ;
834
+ private static readonly Histogram < float > s_waitTimeHistory = ActivitySourceHelper . Meter . CreateHistogram < float > ( "db.client.connections.wait_time" ,
835
+ unit : "ms" , description : "The time it took to obtain an open connection from the pool." ) ;
778
836
private static readonly ConcurrentDictionary < string , ConnectionPool ? > s_pools = new ( ) ;
779
837
private static readonly Action < ILogger , int , string , Exception ? > s_createdNewSession = LoggerMessage . Define < int , string > (
780
838
LogLevel . Debug , new EventId ( EventIds . PoolCreatedNewSession , nameof ( EventIds . PoolCreatedNewSession ) ) ,
@@ -787,6 +845,7 @@ private static void OnAppDomainShutDown(object? sender, EventArgs e) =>
787
845
788
846
private readonly ILogger m_logger ;
789
847
private readonly ILogger m_connectionLogger ;
848
+ private readonly KeyValuePair < string , object ? > [ ] m_stateTagList ;
790
849
private readonly SemaphoreSlim m_cleanSemaphore ;
791
850
private readonly SemaphoreSlim m_sessionSemaphore ;
792
851
private readonly LinkedList < ServerSession > m_sessions ;
0 commit comments