Skip to content

Commit 115b869

Browse files
committed
Add 'db.client.connections.timeouts' counter. Fixes #1392
1 parent cd76fd3 commit 115b869

File tree

4 files changed

+61
-0
lines changed

4 files changed

+61
-0
lines changed

docs/content/diagnostics/metrics.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Name | Type | Unit | Description
3434
--- | --- | --- | ---
3535
`db.client.connections.usage` | UpDownCounter | `{connection}` | The number of connections that are currently in the state described by the state tag.
3636
`db.client.connections.pending_requests` | UpDownCounter | `{request}` | The number of pending requests for an open connection, cumulative for the entire pool.
37+
`db.client.connections.timeouts` | Counter | `{timeout}` | The number of connection timeouts that have occurred trying to obtain a connection from the pool.
3738
`db.client.connections.create_time` | Histogram | `s` | The time it took to create a new connection.
3839
`db.client.connections.use_time` | Histogram | `s` | The time between borrowing a connection and returning it to the pool.
3940
`db.client.connections.wait_time` | Histogram | `s` | The time it took to obtain an open connection from the pool.

src/MySqlConnector/Core/MetricsReporter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ internal static class MetricsReporter
99
public static void RemoveIdle(ConnectionPool pool) => s_connectionsUsageCounter.Add(-1, pool.IdleStateTagList);
1010
public static void AddUsed(ConnectionPool pool) => s_connectionsUsageCounter.Add(1, pool.UsedStateTagList);
1111
public static void RemoveUsed(ConnectionPool pool) => s_connectionsUsageCounter.Add(-1, pool.UsedStateTagList);
12+
public static void AddTimeout(ConnectionPool? pool, ConnectionSettings connectionSettings) => s_connectionTimeouts.Add(1, new KeyValuePair<string, object?>("pool.name", pool?.Name ?? connectionSettings.ConnectionStringBuilder.GetConnectionString(includePassword: false)));
1213
public static void RecordCreateTime(ConnectionPool pool, double seconds) => s_createTimeHistory.Record(seconds, pool.PoolNameTagList);
1314
public static void RecordUseTime(ConnectionPool pool, double seconds) => s_useTimeHistory.Record(seconds, pool.PoolNameTagList);
1415
public static void RecordWaitTime(ConnectionPool pool, double seconds) => s_waitTimeHistory.Record(seconds, pool.PoolNameTagList);
@@ -48,6 +49,8 @@ static IEnumerable<Measurement<int>> GetMinimumConnections() =>
4849
unit: "{connection}", description: "The number of connections that are currently in the state described by the state tag.");
4950
private static readonly UpDownCounter<int> s_pendingRequestsCounter = ActivitySourceHelper.Meter.CreateUpDownCounter<int>("db.client.connections.pending_requests",
5051
unit: "{request}", description: "The number of pending requests for an open connection, cumulative for the entire pool.");
52+
private static readonly Counter<int> s_connectionTimeouts = ActivitySourceHelper.Meter.CreateCounter<int>("db.client.connections.timeouts",
53+
unit: "{timeout}", description: "The number of connection timeouts that have occurred trying to obtain a connection from the pool.");
5154
private static readonly Histogram<double> s_createTimeHistory = ActivitySourceHelper.Meter.CreateHistogram<double>("db.client.connections.create_time",
5255
unit: "s", description: "The time it took to create a new connection.");
5356
private static readonly Histogram<double> s_useTimeHistory = ActivitySourceHelper.Meter.CreateHistogram<double>("db.client.connections.use_time",

src/MySqlConnector/MySqlConnection.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,13 +941,20 @@ private async ValueTask<ServerSession> CreateSessionAsync(ConnectionPool? pool,
941941
}
942942
catch (OperationCanceledException) when (timeoutSource?.IsCancellationRequested is true)
943943
{
944+
MetricsReporter.AddTimeout(pool, connectionSettings);
944945
var messageSuffix = (pool?.IsEmpty is true) ? " All pooled connections are in use." : "";
945946
throw new MySqlException(MySqlErrorCode.UnableToConnectToHost, "Connect Timeout expired." + messageSuffix);
946947
}
947948
catch (MySqlException ex) when ((timeoutSource?.IsCancellationRequested is true) || (ex.ErrorCode == MySqlErrorCode.CommandTimeoutExpired))
948949
{
950+
MetricsReporter.AddTimeout(pool, connectionSettings);
949951
throw new MySqlException(MySqlErrorCode.UnableToConnectToHost, "Connect Timeout expired.", ex);
950952
}
953+
catch (MySqlException ex) when (ex.ErrorCode == MySqlErrorCode.UnableToConnectToHost && ex.Message == "Connect Timeout expired.")
954+
{
955+
MetricsReporter.AddTimeout(pool, connectionSettings);
956+
throw;
957+
}
951958
finally
952959
{
953960
MetricsReporter.RemovePendingRequest(pool);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
namespace MySqlConnector.Tests.Metrics;
2+
3+
public class ConnectTimeoutTests : MetricsTestsBase
4+
{
5+
[Fact(Skip = MetricsSkip)]
6+
public async Task ConnectTimeout()
7+
{
8+
var csb = CreateConnectionStringBuilder();
9+
csb.ConnectionTimeout = 1;
10+
csb.Server = "www.example.com";
11+
PoolName = csb.GetConnectionString(includePassword: false);
12+
13+
using var connection = new MySqlConnection(csb.ConnectionString);
14+
await Assert.ThrowsAsync<MySqlException>(connection.OpenAsync);
15+
16+
AssertMeasurement("db.client.connections.timeouts", 1);
17+
}
18+
19+
[Fact(Skip = MetricsSkip)]
20+
public async Task DataSourceConnectTimeout()
21+
{
22+
var csb = CreateConnectionStringBuilder();
23+
csb.ConnectionTimeout = 1;
24+
csb.Server = "www.example.com";
25+
26+
PoolName = "timeout";
27+
using var dataSource = new MySqlDataSourceBuilder(csb.ConnectionString)
28+
.UseName(PoolName)
29+
.Build();
30+
31+
await Assert.ThrowsAsync<MySqlException>(async () => await dataSource.OpenConnectionAsync());
32+
33+
AssertMeasurement("db.client.connections.timeouts", 1);
34+
}
35+
36+
[Fact(Skip = MetricsSkip)]
37+
public async Task NoPoolConnectTimeout()
38+
{
39+
var csb = CreateConnectionStringBuilder();
40+
csb.ConnectionTimeout = 1;
41+
csb.Server = "www.example.com";
42+
csb.Pooling = false;
43+
PoolName = csb.GetConnectionString(includePassword: false);
44+
45+
using var connection = new MySqlConnection(csb.ConnectionString);
46+
await Assert.ThrowsAsync<MySqlException>(connection.OpenAsync);
47+
48+
AssertMeasurement("db.client.connections.timeouts", 1);
49+
}
50+
}

0 commit comments

Comments
 (0)