Skip to content

Commit a695ce6

Browse files
authored
feat: Instrument OpenAsync() for SQL libraries. (#1725)
1 parent dd490dc commit a695ce6

File tree

14 files changed

+157
-38
lines changed

14 files changed

+157
-38
lines changed

src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs

+2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ public static void Initialize(string installPathExtensionsDirectory)
6262
{ "DataReaderWrapperAsync", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") },
6363

6464
{ "OpenConnectionTracer", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") },
65+
{ "OpenConnectionTracerAsync", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") },
6566
{ "OpenConnectionWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") },
67+
{ "OpenConnectionWrapperAsync", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Sql.dll") },
6668

6769
//The NewRelic.Providers.Wrapper.SerilogLogging.dll depends on the Serilog.dll; therefore, it should
6870
//only be loaded by the agent when Serilog is used otherwise assembly load exception will occur.

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/Instrumentation.xml

+45-1
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,11 @@ SPDX-License-Identifier: Apache-2.0
368368
<match assemblyName="MySql.Data" className="MySql.Data.MySqlClient.MySqlConnection">
369369
<exactMethodMatcher methodName="Open"/>
370370
</match>
371-
371+
<!-- Up until 8.0.33, OpenAsync was not actually async -->
372+
<match assemblyName="MySql.Data" className="MySql.Data.MySqlClient.MySqlConnection" maxVersion="8.0.33">
373+
<exactMethodMatcher methodName="OpenAsync"/>
374+
</match>
375+
372376
<!-- MySqlConnector 0.x -->
373377
<match assemblyName="MySqlConnector" className="MySql.Data.MySqlClient.MySqlConnection">
374378
<exactMethodMatcher methodName="Open" />
@@ -390,5 +394,45 @@ SPDX-License-Identifier: Apache-2.0
390394
</match>
391395
</tracerFactory>
392396

397+
<!-- DbConnection.OpenAsync() -->
398+
<tracerFactory name="OpenConnectionTracerAsync">
399+
<!-- built in MS SQL driver (framework) -->
400+
<match assemblyName="System.Data" className="System.Data.SqlClient.SqlConnection">
401+
<exactMethodMatcher methodName="OpenAsync" parameters="System.Threading.CancellationToken" />
402+
</match>
403+
404+
<!-- built in MS SQL driver (core / nuget) -->
405+
<match assemblyName="System.Data.SqlClient" className="System.Data.SqlClient.SqlConnection">
406+
<exactMethodMatcher methodName="OpenAsync" parameters="System.Threading.CancellationToken" />
407+
</match>
408+
409+
<!-- MS SQL flagship data access driver -->
410+
<match assemblyName="Microsoft.Data.SqlClient" className="Microsoft.Data.SqlClient.SqlConnection">
411+
<exactMethodMatcher methodName="OpenAsync" parameters="System.Threading.CancellationToken" />
412+
</match>
413+
414+
<!-- MySql (official) driver -->
415+
<!-- Up until 8.0.33, OpenAsync was not actually async -->
416+
<match assemblyName="MySql.Data" className="MySql.Data.MySqlClient.MySqlConnection" minVersion="8.0.33">
417+
<exactMethodMatcher methodName="OpenAsync" parameters="System.Boolean,System.Threading.CancellationToken"/>
418+
</match>
419+
420+
<!-- MySqlConnector 0.x -->
421+
<match assemblyName="MySqlConnector" className="MySql.Data.MySqlClient.MySqlConnection">
422+
<exactMethodMatcher methodName="OpenAsync" parameters="System.Nullable`1[MySqlConnector.Protocol.Serialization.IOBehavior],System.Threading.CancellationToken" />
423+
</match>
424+
425+
<!-- MySqlConnector 1.x -->
426+
<match assemblyName="MySqlConnector" className="MySqlConnector.MySqlConnection">
427+
<exactMethodMatcher methodName="OpenAsync" parameters="System.Nullable`1[MySqlConnector.Protocol.Serialization.IOBehavior],System.Threading.CancellationToken" />
428+
</match>
429+
430+
<!-- Npgsql Postgres data provider -->
431+
<match assemblyName="Npgsql" className="Npgsql.NpgsqlConnection">
432+
<exactMethodMatcher methodName="OpenAsync" parameters="System.Threading.CancellationToken"/>
433+
</match>
434+
</tracerFactory>
435+
436+
393437
</instrumentation>
394438
</extension>

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Sql/OpenConnectionWrapper.cs

+42-5
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,67 @@
55
using NewRelic.Agent.Extensions.Providers.Wrapper;
66
using System;
77
using System.Linq;
8+
using System.Threading.Tasks;
89

910
namespace NewRelic.Providers.Wrapper.Sql
1011
{
11-
public class OpenConnectionWrapper : IWrapper
12+
public class OpenConnectionWrapper : OpenConnectionWrapperBase
1213
{
13-
public static readonly string[] WrapperNames =
14+
private static readonly string[] _tracerNames =
1415
{
1516
"OpenConnectionTracer",
16-
"OpenConnectionWrapper"
17+
"OpenConnectionWrapper",
1718
};
1819

20+
public override string[] WrapperNames => _tracerNames;
21+
public override bool ExecuteAsAsync => false;
22+
}
23+
24+
public class OpenConnectionAsyncWrapper : OpenConnectionWrapperBase
25+
{
26+
private static readonly string[] _tracerNames =
27+
{
28+
"OpenConnectionTracerAsync",
29+
"OpenConnectionWrapperAsync"
30+
};
31+
public override string[] WrapperNames => _tracerNames;
32+
public override bool ExecuteAsAsync => true;
33+
}
34+
35+
36+
public abstract class OpenConnectionWrapperBase : IWrapper
37+
{
38+
public abstract string[] WrapperNames { get; }
39+
40+
public abstract bool ExecuteAsAsync { get; }
41+
1942
public bool IsTransactionRequired => true;
2043

2144
public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo)
2245
{
23-
return new CanWrapResponse(WrapperNames.Contains(methodInfo.RequestedWrapperName, StringComparer.OrdinalIgnoreCase));
46+
var canWrap = WrapperNames.Contains(methodInfo.RequestedWrapperName, StringComparer.OrdinalIgnoreCase);
47+
if (canWrap && ExecuteAsAsync)
48+
{
49+
var method = methodInfo.Method;
50+
return TaskFriendlySyncContextValidator.CanWrapAsyncMethod(method.Type.Assembly.GetName().Name, method.Type.FullName, method.MethodName);
51+
}
52+
53+
return new CanWrapResponse(canWrap);
2454
}
2555

2656
public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
2757
{
58+
if (instrumentedMethodCall.IsAsync)
59+
{
60+
transaction.AttachToAsync();
61+
}
62+
2863
var typeName = instrumentedMethodCall.MethodCall.Method.Type.FullName ?? "unknown";
2964
var segment = transaction.StartMethodSegment(instrumentedMethodCall.MethodCall, typeName, instrumentedMethodCall.MethodCall.Method.MethodName, isLeaf: true);
3065

31-
return Delegates.GetDelegateFor(segment);
66+
return ExecuteAsAsync
67+
? Delegates.GetAsyncDelegateFor<Task>(agent, segment)
68+
: Delegates.GetDelegateFor(segment);
3269
}
3370
}
3471
}

tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/MicrosoftDataSqlClientExerciser.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public async Task<string> MsSqlAsync(string tableName)
8686

8787
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
8888
{
89-
connection.Open();
89+
await connection.OpenAsync();
9090

9191
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection))
9292
{
@@ -166,7 +166,7 @@ public async Task<string> MsSqlAsync_WithParameterizedQuery(string tableName, bo
166166

167167
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
168168
{
169-
connection.Open();
169+
await connection.OpenAsync();
170170

171171
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection))
172172
{

tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataExerciser.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public async Task<string> MsSqlAsync(string tableName)
8888

8989
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
9090
{
91-
connection.Open();
91+
await connection.OpenAsync();
9292

9393
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection))
9494
{
@@ -169,7 +169,7 @@ public async Task<string> MsSqlAsync_WithParameterizedQuery(string tableName, bo
169169

170170
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
171171
{
172-
connection.Open();
172+
await connection.OpenAsync();
173173

174174
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection))
175175
{

tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MsSql/SystemDataSqlClientExerciser.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public async Task<string> MsSqlAsync(string tableName)
8888

8989
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
9090
{
91-
connection.Open();
91+
await connection.OpenAsync();
9292

9393
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection))
9494
{
@@ -169,7 +169,7 @@ public async Task<string> MsSqlAsync_WithParameterizedQuery(string tableName, bo
169169

170170
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
171171
{
172-
connection.Open();
172+
await connection.OpenAsync();
173173

174174
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection))
175175
{

tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlConnectorExerciser.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ private async Task<string> ExecuteCommandAsync(Func<MySqlCommand, Task<string>>
215215
using (var connection = new MySqlConnection(MySqlTestConfiguration.MySqlConnectionString))
216216
using (var command = new MySqlCommand("SELECT _date FROM dates WHERE _date LIKE '2%' ORDER BY _date DESC LIMIT 1", connection))
217217
{
218-
connection.Open();
218+
await connection.OpenAsync();
219219
result = await action(command);
220220
}
221221

tests/Agent/IntegrationTests/SharedApplications/Common/MultiFunctionApplicationHelpers/NetStandardLibraries/MySql/MySqlExerciser.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public async Task SingleDateQueryAsync()
4848
using (var connection = new MySqlConnection(MySqlTestConfiguration.MySqlConnectionString))
4949
using (var command = new MySqlCommand("SELECT _date FROM dates WHERE _date LIKE '2%' ORDER BY _date DESC LIMIT 10000", connection))
5050
{
51-
connection.Open();
51+
await connection.OpenAsync();
5252
using (var reader = await command.ExecuteReaderAsync())
5353
{
5454
while (await reader.ReadAsync())

tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcApplication/Controllers/OracleController.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public async Task<string> OracleAsync(string tableName)
7676

7777
using (var connection = new OracleConnection(connectionString))
7878
{
79-
connection.Open();
79+
await connection.OpenAsync();
8080

8181
using (var command = new OracleCommand("SELECT DEGREE FROM user_tables WHERE ROWNUM <= 1", connection))
8282
{

tests/Agent/IntegrationTests/UnboundedApplications/BasicMvcCoreApplication/Controllers/MicrosoftDataSqlClientController.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public async Task<string> MsSqlAsync(string tableName)
7575

7676
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
7777
{
78-
connection.Open();
78+
await connection.OpenAsync();
7979

8080
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = 'John'", connection))
8181
{
@@ -153,7 +153,7 @@ public async Task<string> MsSqlAsync_WithParameterizedQuery(string tableName, bo
153153

154154
using (var connection = new SqlConnection(MsSqlConfiguration.MsSqlConnectionString))
155155
{
156-
connection.Open();
156+
await connection.OpenAsync();
157157

158158
using (var command = new SqlCommand("SELECT * FROM NewRelic.dbo.TeamMembers WHERE FirstName = @FN", connection))
159159
{

tests/Agent/IntegrationTests/UnboundedIntegrationTests/MsSql/MsSqlAsyncTests.cs

+22-9
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ public abstract class MsSqlAsyncTestsBase<TFixture> : NewRelicIntegrationTest<TF
2222
private readonly string _expectedTransactionName;
2323

2424
private readonly string _tableName;
25+
private readonly string _libraryName;
2526

26-
public MsSqlAsyncTestsBase(TFixture fixture, ITestOutputHelper output, string excerciserName) : base(fixture)
27+
public MsSqlAsyncTestsBase(TFixture fixture, ITestOutputHelper output, string excerciserName, string libraryName) : base(fixture)
2728
{
2829
_fixture = fixture;
2930
_fixture.TestLogger = output;
3031
_expectedTransactionName = $"OtherTransaction/Custom/MultiFunctionApplicationHelpers.NetStandardLibraries.MsSql.{excerciserName}/MsSqlAsync";
3132
_tableName = Utilities.GenerateTableName();
33+
_libraryName = libraryName;
3234

3335
_fixture.AddCommand($"{excerciserName} CreateTable {_tableName}");
3436
_fixture.AddCommand($"{excerciserName} MsSqlAsync {_tableName}");
@@ -81,6 +83,7 @@ public void Test()
8183
new Assertions.ExpectedMetric { metricName = @"Datastore/MSSQL/all", callCount = expectedDatastoreCallCount },
8284
new Assertions.ExpectedMetric { metricName = @"Datastore/allOther", callCount = expectedDatastoreCallCount },
8385

86+
new Assertions.ExpectedMetric { metricName = $"DotNet/{_libraryName}.SqlConnection/OpenAsync", callCount = 1 },
8487
new Assertions.ExpectedMetric { metricName = @"Datastore/MSSQL/allOther", callCount = expectedDatastoreCallCount },
8588
new Assertions.ExpectedMetric { metricName = $@"Datastore/instance/MSSQL/{CommonUtils.NormalizeHostname(MsSqlConfiguration.MsSqlServer)}/default", callCount = expectedDatastoreCallCount},
8689
new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/select", callCount = 2 },
@@ -103,7 +106,10 @@ public void Test()
103106
// The operation metric should not be scoped because the statement metric is scoped instead
104107
new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/select", callCount = 3, metricScope = _expectedTransactionName },
105108
new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/insert", callCount = 1, metricScope = _expectedTransactionName },
106-
new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/delete", callCount = 1, metricScope = _expectedTransactionName }
109+
new Assertions.ExpectedMetric { metricName = @"Datastore/operation/MSSQL/delete", callCount = 1, metricScope = _expectedTransactionName },
110+
111+
// Don't double count the open
112+
new Assertions.ExpectedMetric { metricName = $"DotNet/{_libraryName}.SqlConnection/Open" },
107113
};
108114
var expectedTransactionTraceSegments = new List<string>
109115
{
@@ -187,7 +193,8 @@ public MsSqlAsyncTests_SystemData_FWLatest(ConsoleDynamicMethodFixtureFWLatest f
187193
: base(
188194
fixture: fixture,
189195
output: output,
190-
excerciserName: "SystemDataExerciser")
196+
excerciserName: "SystemDataExerciser",
197+
libraryName: "System.Data.SqlClient")
191198
{
192199
}
193200
}
@@ -199,7 +206,8 @@ public MsSqlAsyncTests_SystemDataSqlClient_CoreLatest(ConsoleDynamicMethodFixtur
199206
: base(
200207
fixture: fixture,
201208
output: output,
202-
excerciserName: "SystemDataSqlClientExerciser")
209+
excerciserName: "SystemDataSqlClientExerciser",
210+
libraryName: "System.Data.SqlClient")
203211
{
204212
}
205213
}
@@ -211,7 +219,8 @@ public MsSqlAsyncTests_SystemDataSqlClient_CoreOldest(ConsoleDynamicMethodFixtur
211219
: base(
212220
fixture: fixture,
213221
output: output,
214-
excerciserName: "SystemDataSqlClientExerciser")
222+
excerciserName: "SystemDataSqlClientExerciser",
223+
libraryName: "System.Data.SqlClient")
215224
{
216225
}
217226
}
@@ -223,7 +232,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_FWLatest(ConsoleDynamicMethodFixtu
223232
: base(
224233
fixture: fixture,
225234
output: output,
226-
excerciserName: "MicrosoftDataSqlClientExerciser")
235+
excerciserName: "MicrosoftDataSqlClientExerciser",
236+
libraryName: "Microsoft.Data.SqlClient")
227237
{
228238
}
229239
}
@@ -235,7 +245,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_FW462(ConsoleDynamicMethodFixtureF
235245
: base(
236246
fixture: fixture,
237247
output: output,
238-
excerciserName: "MicrosoftDataSqlClientExerciser")
248+
excerciserName: "MicrosoftDataSqlClientExerciser",
249+
libraryName: "Microsoft.Data.SqlClient")
239250
{
240251
}
241252
}
@@ -248,7 +259,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_CoreLatest(ConsoleDynamicMethodFix
248259
: base(
249260
fixture: fixture,
250261
output: output,
251-
excerciserName: "MicrosoftDataSqlClientExerciser")
262+
excerciserName: "MicrosoftDataSqlClientExerciser",
263+
libraryName: "Microsoft.Data.SqlClient")
252264
{
253265
}
254266
}
@@ -260,7 +272,8 @@ public MsSqlAsyncTests_MicrosoftDataSqlClient_CoreOldest(ConsoleDynamicMethodFix
260272
: base(
261273
fixture: fixture,
262274
output: output,
263-
excerciserName: "MicrosoftDataSqlClientExerciser")
275+
excerciserName: "MicrosoftDataSqlClientExerciser",
276+
libraryName: "Microsoft.Data.SqlClient")
264277
{
265278
}
266279
}

0 commit comments

Comments
 (0)