From 56dd2064a6e4f2ca9bc2de306c14606d35e4a834 Mon Sep 17 00:00:00 2001 From: Lucas Pimentel-Ordyna Date: Wed, 16 Oct 2019 21:47:59 -0400 Subject: [PATCH] Expand ADO.NET instrumentation (DbCommand, SqlCommand) (#516) * rewrite SqlServer sample app * clean up code * clean up AdoNetIntegration * add new SqlClientIntegration * intercept more method calls in AdoNetIntegration * make StyleCop allow custom ordering in integrations * clean up xml-doc comments, add missing ones * add Samples.DbCommand project * fix SqlServerTests, add DbCommandTests * fix bad merge * run DbCommand integration tests * fix build for netcoreapp3.0 * rename Samples.SqlServer to Samples.SqlCommand everywhere * fix target type * refactor ExecuteReader() overloads * fix filters * rename method * set project dependencies so managed profiler assembly is built when building samples in VS * refactor common logic in db tests into Samples.DatabaseHelper project * fix sample env vars * sleep for a short time between sync and async method calls * override target method name * fix signature * fix target signature Co-Authored-By: Zach Montoya * group ado.net tests into their own folder * regenerate integrations and package version files * fix span counts and filter by operation name * fix timezone bug that caused tests to fail in Paris * rename test harness class * remove Samples.DbCommand and DbCommandTests, rename Samples.SqlCommand to Samples.SqlServer * update package versions * delete unused file * refactor creating new connections * allow skipping some callbacks * refactor MySql and Npgsql integrations to use the new db test hardness * add new integration tests for MySql and Npgsql * add assertions for DbType tag * missed a few renames from Samples.SqlCommand to Samples.SqlServer * remove Samples.DbCommand from docker-compose and scripts * move AdoNet files into their own directory * fix namespaces * rename AdoNetIntegration to DbCommandIntegration * fix package name Co-Authored-By: Zach Montoya * remove "RunOnWindows" trait * RunOnWindows * fix sample name Co-Authored-By: Zach Montoya * add comment * delete file that was moved into the AdoNet folder * build Samples.MySql before running tests * test single version of MySql client for now * fix errors from bad merge * update sql server docker image * update comment * add more tags to manual spans * add a root span for the entire sample execution * fix span names, assert on expected span count * sign into default sql server database * run Npgsql (PostgreSQL) test only on single library version until we have a proper integration (like MySQL) * fix default database name * refactor tests to use more constants and fix copy/paste mistakes * change query to work in PostgreSQL * use correct MySql database name * skip the new MySQL tests for now --- Datadog.Trace.sln | 20 + PackageVersions.g.props | 43 +- PackageVersionsGeneratorDefinitions.json | 8 +- docker-compose.yml | 10 +- docker/build.sh | 2 +- integrations.json | 1141 +++++++++++++---- .../Samples.DatabaseHelper/Extensions.cs | 32 + .../RelationalDatabaseTestHarness.cs | 286 +++++ .../Samples.DatabaseHelper.csproj | 12 + samples/Samples.MySql/Program.cs | 109 +- samples/Samples.MySql/Samples.MySql.csproj | 4 + samples/Samples.Npgsql/Program.cs | 133 +- samples/Samples.Npgsql/Samples.Npgsql.csproj | 4 + samples/Samples.SqlServer/Models.cs | 41 - samples/Samples.SqlServer/Program.cs | 84 +- .../Samples.SqlServer.csproj | 16 +- .../GlobalSuppressions.cs | 9 + .../Integrations/AdoNet/AdoNetConstants.cs | 30 + .../AdoNet/DbCommandIntegration.cs | 484 +++++++ .../AdoNet/SqlCommandIntegration.cs | 472 +++++++ .../Integrations/AdoNetIntegration.cs | 248 ---- .../ScopeFactory.cs | 78 ++ .../AdoNet/MySqlCommandTests.cs | 43 + .../AdoNet/NpgsqlCommandTests.cs | 43 + .../SqlCommandTests.cs} | 28 +- .../NpgSqlTests.cs | 37 - .../PackageVersions.g.cs | 33 +- .../MockTracerAgent.cs | 6 +- 28 files changed, 2578 insertions(+), 878 deletions(-) create mode 100644 sample-libs/Samples.DatabaseHelper/Extensions.cs create mode 100644 sample-libs/Samples.DatabaseHelper/RelationalDatabaseTestHarness.cs create mode 100644 sample-libs/Samples.DatabaseHelper/Samples.DatabaseHelper.csproj delete mode 100644 samples/Samples.SqlServer/Models.cs create mode 100644 src/Datadog.Trace.ClrProfiler.Managed/GlobalSuppressions.cs create mode 100644 src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/AdoNetConstants.cs create mode 100644 src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/DbCommandIntegration.cs create mode 100644 src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/SqlCommandIntegration.cs delete mode 100644 src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs create mode 100644 test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs create mode 100644 test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs rename test/Datadog.Trace.ClrProfiler.IntegrationTests/{SqlServerTests.cs => AdoNet/SqlCommandTests.cs} (51%) delete mode 100644 test/Datadog.Trace.ClrProfiler.IntegrationTests/NpgSqlTests.cs diff --git a/Datadog.Trace.sln b/Datadog.Trace.sln index 9b2946de99aa..7df5c4403902 100644 --- a/Datadog.Trace.sln +++ b/Datadog.Trace.sln @@ -40,6 +40,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution integrations.json = integrations.json LICENSE = LICENSE LICENSE-3rdparty.csv = LICENSE-3rdparty.csv + PackageVersionsGeneratorDefinitions.json = PackageVersionsGeneratorDefinitions.json docs\README.md = docs\README.md stylecop.json = stylecop.json EndProjectSection @@ -114,6 +115,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.ClrProfiler.I EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.SqlServer", "samples\Samples.SqlServer\Samples.SqlServer.csproj", "{086FF8A0-9CEE-470A-9751-78B0F1340649}" + ProjectSection(ProjectDependencies) = postProject + {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} = {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} + {85F35AAF-D102-4960-8B41-3BD9CBD0E77F} = {85F35AAF-D102-4960-8B41-3BD9CBD0E77F} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Elasticsearch", "samples\Samples.Elasticsearch\Samples.Elasticsearch.csproj", "{C98950B1-DC4B-43DA-974F-EF2CF325EC2B}" ProjectSection(ProjectDependencies) = postProject @@ -251,6 +256,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.WcfClient", "sample EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.Elasticsearch.MultipleAppDomains", "samples\Samples.Elasticsearch.MultipleAppDomains\Samples.Elasticsearch.MultipleAppDomains.csproj", "{48283691-0D4B-4ABD-B75B-EDFC682E1547}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.DatabaseHelper", "sample-libs\Samples.DatabaseHelper\Samples.DatabaseHelper.csproj", "{472DBA92-4FEA-4B9A-BA70-0E97B942E12D}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UpdateVendors", "tools\UpdateVendors\UpdateVendors.csproj", "{72FB583A-A1B0-4B5F-8658-617B100DCD8E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SynchronizeInstaller", "tools\SynchronizeInstaller\SynchronizeInstaller.csproj", "{EBDDF6D7-59B8-4831-80C6-458C1ABBFC63}" @@ -815,6 +822,18 @@ Global {48283691-0D4B-4ABD-B75B-EDFC682E1547}.Release|x64.Build.0 = Release|x64 {48283691-0D4B-4ABD-B75B-EDFC682E1547}.Release|x86.ActiveCfg = Release|x86 {48283691-0D4B-4ABD-B75B-EDFC682E1547}.Release|x86.Build.0 = Release|x86 + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Debug|x64.ActiveCfg = Debug|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Debug|x64.Build.0 = Debug|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Debug|x86.ActiveCfg = Debug|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Debug|x86.Build.0 = Debug|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Release|Any CPU.Build.0 = Release|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Release|x64.ActiveCfg = Release|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Release|x64.Build.0 = Release|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Release|x86.ActiveCfg = Release|Any CPU + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D}.Release|x86.Build.0 = Release|Any CPU {72FB583A-A1B0-4B5F-8658-617B100DCD8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {72FB583A-A1B0-4B5F-8658-617B100DCD8E}.Debug|Any CPU.Build.0 = Debug|Any CPU {72FB583A-A1B0-4B5F-8658-617B100DCD8E}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -897,6 +916,7 @@ Global {E1706893-D3A5-43B9-9036-AEF49DB9600B} = {550AE553-2BBB-4021-B55A-137EF31A6B1F} {21D420EB-06D0-489D-A71C-C748BB46B8EF} = {AA6F5582-3B71-49AC-AA39-8F7815AC46BE} {48283691-0D4B-4ABD-B75B-EDFC682E1547} = {AA6F5582-3B71-49AC-AA39-8F7815AC46BE} + {472DBA92-4FEA-4B9A-BA70-0E97B942E12D} = {FA03944C-2391-4C25-8979-2E078A8CE0DD} {72FB583A-A1B0-4B5F-8658-617B100DCD8E} = {5D8E1F81-B820-4736-B797-271B0FE787EE} {EBDDF6D7-59B8-4831-80C6-458C1ABBFC63} = {5D8E1F81-B820-4736-B797-271B0FE787EE} EndGlobalSection diff --git a/PackageVersions.g.props b/PackageVersions.g.props index f710cb9cece9..f0c84fac23c1 100644 --- a/PackageVersions.g.props +++ b/PackageVersions.g.props @@ -150,6 +150,9 @@ NOTE: This code was generated by the GeneratePackageVersions tool. To safely ApiVersion=6.8.2;RestoreRecursive=false;BuildProjectReferences=false + + ApiVersion=6.8.3;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=5.3.0;RestoreRecursive=false;BuildProjectReferences=false @@ -219,56 +222,44 @@ NOTE: This code was generated by the GeneratePackageVersions tool. To safely ApiVersion=4.1.0;RestoreRecursive=false;BuildProjectReferences=false - - ApiVersion=2.0.0;RestoreRecursive=false;BuildProjectReferences=false - - - ApiVersion=2.0.1;RestoreRecursive=false;BuildProjectReferences=false - - - ApiVersion=2.0.2;RestoreRecursive=false;BuildProjectReferences=false - - - ApiVersion=2.0.3;RestoreRecursive=false;BuildProjectReferences=false - - - ApiVersion=2.1.0;RestoreRecursive=false;BuildProjectReferences=false + + ApiVersion=4.1.1;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.1.1;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.1.0;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.1.2;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.3.0;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.1.3;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.3.1;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.1.4;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.4.0;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.1.8;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.4.1;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.1.11;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.4.2;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.2.0;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.4.3;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.2.1;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.5.0;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.2.2;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.5.1;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.2.3;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.6.0;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.2.4;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.6.1;RestoreRecursive=false;BuildProjectReferences=false - ApiVersion=2.2.6;RestoreRecursive=false;BuildProjectReferences=false + ApiVersion=4.7.0;RestoreRecursive=false;BuildProjectReferences=false ApiVersion=1.0.187;RestoreRecursive=false;BuildProjectReferences=false diff --git a/PackageVersionsGeneratorDefinitions.json b/PackageVersionsGeneratorDefinitions.json index 6640db7810c2..dde61cd8b875 100644 --- a/PackageVersionsGeneratorDefinitions.json +++ b/PackageVersionsGeneratorDefinitions.json @@ -28,11 +28,11 @@ "MaxVersionExclusive": "5.0.0" }, { - "IntegrationName": "SqlServer", + "IntegrationName": "SqlClient", "SampleProjectName": "Samples.SqlServer", - "NugetPackageSearchName": "Microsoft.EntityFrameworkCore.SqlServer", - "MinVersion": "2.0.0", - "MaxVersionExclusive": "3.0.0" + "NugetPackageSearchName": "System.Data.SqlClient", + "MinVersion": "4.1.0", + "MaxVersionExclusive": "5.0.0" }, { "IntegrationName": "StackExchangeRedis", diff --git a/docker-compose.yml b/docker-compose.yml index b57fbfa87931..01577180bc08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,13 +54,13 @@ services: - "127.0.0.1:3306:3306" sqlserver: - image: microsoft/mssql-server-linux:latest + image: mcr.microsoft.com/mssql/server:latest ports: - "127.0.0.1:1433:1433" environment: - ACCEPT_EULA=Y - SA_PASSWORD=Strong!Passw0rd - + wcfservice: image: mcr.microsoft.com/dotnet/framework/wcf:4.8 ports: @@ -158,7 +158,7 @@ services: volumes: - ./:/project environment: - - SQLSERVER_CONNECTION_STRING=Server=sqlserver;Database=BlogDatabase;User=sa;Password=Strong!Passw0rd + - SQLSERVER_CONNECTION_STRING=Server=sqlserver;User=sa;Password=Strong!Passw0rd depends_on: - sqlserver @@ -174,7 +174,7 @@ services: - POSTGRES_HOST=postgres depends_on: - postgres - + Samples.MySql: build: context: ./ @@ -213,7 +213,7 @@ services: - STACKEXCHANGE_REDIS_HOST=stackexchangeredis:6379 - ELASTICSEARCH6_HOST=elasticsearch6:9200 - ELASTICSEARCH5_HOST=elasticsearch5:9200 - - SQLSERVER_CONNECTION_STRING=Server=sqlserver;Database=BlogDatabase;User=sa;Password=Strong!Passw0rd + - SQLSERVER_CONNECTION_STRING=Server=sqlserver;User=sa;Password=Strong!Passw0rd - POSTGRES_HOST=postgres - MYSQL_HOST=mysql - buildConfiguration=${buildConfiguration:-Debug} diff --git a/docker/build.sh b/docker/build.sh index b300e226b7bc..1c04f002b74e 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -22,7 +22,7 @@ then dotnet publish -f $publishTargetFramework -c $buildConfiguration samples/Samples.AspNetCoreMvc2/Samples.AspNetCoreMvc2.csproj -p:Configuration=$buildConfiguration -p:ManagedProfilerOutputDirectory="$PUBLISH_OUTPUT" fi -for sample in Samples.Elasticsearch Samples.Elasticsearch.V5 Samples.ServiceStack.Redis Samples.StackExchange.Redis Samples.SqlServer Samples.MongoDB Samples.HttpMessageHandler Samples.Npgsql Samples.GraphQL ; do +for sample in Samples.Elasticsearch Samples.Elasticsearch.V5 Samples.ServiceStack.Redis Samples.StackExchange.Redis Samples.SqlServer Samples.MongoDB Samples.HttpMessageHandler Samples.Npgsql Samples.MySql Samples.GraphQL ; do dotnet publish -f $publishTargetFramework -c $buildConfiguration samples/$sample/$sample.csproj -p:Configuration=$buildConfiguration -p:ManagedProfilerOutputDirectory="$PUBLISH_OUTPUT" done diff --git a/integrations.json b/integrations.json index b8095544fec3..b2932477fbe7 100644 --- a/integrations.json +++ b/integrations.json @@ -1,107 +1,4 @@ [ - { - "name": "AdoNet", - "method_replacements": [ - { - "caller": {}, - "target": { - "assembly": "System.Data", - "type": "System.Data.Common.DbCommand", - "method": "ExecuteDbDataReader", - "signature_types": [ - "System.Data.Common.DbDataReader", - "System.Data.CommandBehavior" - ], - "minimum_major": 4, - "minimum_minor": 0, - "minimum_patch": 0, - "maximum_major": 4, - "maximum_minor": 65535, - "maximum_patch": 65535 - }, - "wrapper": { - "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNetIntegration", - "method": "ExecuteDbDataReader", - "signature": "00 05 1C 1C 08 08 08 0A" - } - }, - { - "caller": {}, - "target": { - "assembly": "System.Data.Common", - "type": "System.Data.Common.DbCommand", - "method": "ExecuteDbDataReader", - "signature_types": [ - "System.Data.Common.DbDataReader", - "System.Data.CommandBehavior" - ], - "minimum_major": 4, - "minimum_minor": 0, - "minimum_patch": 0, - "maximum_major": 4, - "maximum_minor": 65535, - "maximum_patch": 65535 - }, - "wrapper": { - "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNetIntegration", - "method": "ExecuteDbDataReader", - "signature": "00 05 1C 1C 08 08 08 0A" - } - }, - { - "caller": {}, - "target": { - "assembly": "System.Data", - "type": "System.Data.Common.DbCommand", - "method": "ExecuteDbDataReaderAsync", - "signature_types": [ - "System.Threading.Tasks.Task`1", - "System.Data.CommandBehavior", - "System.Threading.CancellationToken" - ], - "minimum_major": 4, - "minimum_minor": 0, - "minimum_patch": 0, - "maximum_major": 4, - "maximum_minor": 65535, - "maximum_patch": 65535 - }, - "wrapper": { - "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNetIntegration", - "method": "ExecuteDbDataReaderAsync", - "signature": "00 06 1C 1C 08 1C 08 08 0A" - } - }, - { - "caller": {}, - "target": { - "assembly": "System.Data.Common", - "type": "System.Data.Common.DbCommand", - "method": "ExecuteDbDataReaderAsync", - "signature_types": [ - "System.Threading.Tasks.Task`1", - "System.Data.CommandBehavior", - "System.Threading.CancellationToken" - ], - "minimum_major": 4, - "minimum_minor": 0, - "minimum_patch": 0, - "maximum_major": 4, - "maximum_minor": 65535, - "maximum_patch": 65535 - }, - "wrapper": { - "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNetIntegration", - "method": "ExecuteDbDataReaderAsync", - "signature": "00 06 1C 1C 08 1C 08 08 0A" - } - } - ] - }, { "name": "AspNetCoreMvc2", "method_replacements": [ @@ -282,192 +179,160 @@ ] }, { - "name": "ElasticsearchNet5", + "name": "DbCommand", "method_replacements": [ { - "caller": { - "assembly": "Elasticsearch.Net" - }, + "caller": {}, "target": { - "assembly": "Elasticsearch.Net", - "type": "Elasticsearch.Net.IRequestPipeline", - "method": "CallElasticsearch", + "assembly": "System.Data", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteReader", "signature_types": [ - "Elasticsearch.Net.ElasticsearchResponse`1", - "Elasticsearch.Net.RequestData" + "System.Data.Common.DbDataReader" ], - "minimum_major": 5, + "minimum_major": 4, "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 5, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet5Integration", - "method": "CallElasticsearch", - "signature": "10 01 05 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteReader", + "signature": "00 04 1C 1C 08 08 0A" } }, { - "caller": { - "assembly": "Elasticsearch.Net" - }, + "caller": {}, "target": { - "assembly": "Elasticsearch.Net", - "type": "Elasticsearch.Net.IRequestPipeline", - "method": "CallElasticsearchAsync", + "assembly": "System.Data.Common", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteReader", "signature_types": [ - "System.Threading.Tasks.Task`1>", - "Elasticsearch.Net.RequestData", - "System.Threading.CancellationToken" + "System.Data.Common.DbDataReader" ], - "minimum_major": 5, + "minimum_major": 4, "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 5, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet5Integration", - "method": "CallElasticsearchAsync", - "signature": "10 01 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteReader", + "signature": "00 04 1C 1C 08 08 0A" } - } - ] - }, - { - "name": "ElasticsearchNet6", - "method_replacements": [ + }, { - "caller": { - "assembly": "Elasticsearch.Net" - }, + "caller": {}, "target": { - "assembly": "Elasticsearch.Net", - "type": "Elasticsearch.Net.IRequestPipeline", - "method": "CallElasticsearch", + "assembly": "System.Data", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteReader", "signature_types": [ - "T", - "Elasticsearch.Net.RequestData" + "System.Data.Common.DbDataReader", + "System.Data.CommandBehavior" ], - "minimum_major": 6, + "minimum_major": 4, "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 6, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet6Integration", - "method": "CallElasticsearch", - "signature": "10 01 05 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteReaderWithBehavior", + "signature": "00 05 1C 1C 08 08 08 0A" } }, { - "caller": { - "assembly": "Elasticsearch.Net" - }, + "caller": {}, "target": { - "assembly": "Elasticsearch.Net", - "type": "Elasticsearch.Net.IRequestPipeline", - "method": "CallElasticsearchAsync", + "assembly": "System.Data.Common", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteReader", "signature_types": [ - "System.Threading.Tasks.Task`1", - "Elasticsearch.Net.RequestData", - "System.Threading.CancellationToken" + "System.Data.Common.DbDataReader", + "System.Data.CommandBehavior" ], - "minimum_major": 6, + "minimum_major": 4, "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 6, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet6Integration", - "method": "CallElasticsearchAsync", - "signature": "10 01 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteReaderWithBehavior", + "signature": "00 05 1C 1C 08 08 08 0A" } - } - ] - }, - { - "name": "GraphQL", - "method_replacements": [ + }, { "caller": {}, "target": { - "assembly": "GraphQL", - "type": "GraphQL.Validation.IDocumentValidator", - "method": "Validate", + "assembly": "System.Data", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteReaderAsync", "signature_types": [ - "GraphQL.Validation.IValidationResult", - "System.String", - "GraphQL.Types.ISchema", - "GraphQL.Language.AST.Document", - "System.Collections.Generic.IEnumerable`1", - "_", - "GraphQL.Inputs" + "System.Threading.Tasks.Task`1", + "System.Data.CommandBehavior", + "System.Threading.CancellationToken" ], - "minimum_major": 2, - "minimum_minor": 3, + "minimum_major": 4, + "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 2, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.GraphQLIntegration", - "method": "Validate", - "signature": "00 0A 1C 1C 1C 1C 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteReaderAsync", + "signature": "00 06 1C 1C 08 1C 08 08 0A" } }, { "caller": {}, "target": { - "assembly": "GraphQL", - "type": "GraphQL.Execution.IExecutionStrategy", - "method": "ExecuteAsync", + "assembly": "System.Data.Common", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteReaderAsync", "signature_types": [ - "System.Threading.Tasks.Task`1", - "GraphQL.Execution.ExecutionContext" + "System.Threading.Tasks.Task`1", + "System.Data.CommandBehavior", + "System.Threading.CancellationToken" ], - "minimum_major": 2, - "minimum_minor": 3, + "minimum_major": 4, + "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 2, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.GraphQLIntegration", - "method": "ExecuteAsync", - "signature": "00 05 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteReaderAsync", + "signature": "00 06 1C 1C 08 1C 08 08 0A" } - } - ] - }, - { - "name": "HttpMessageHandler", - "method_replacements": [ + }, { "caller": {}, "target": { - "assembly": "System.Net.Http", - "type": "System.Net.Http.HttpMessageHandler", - "method": "SendAsync", + "assembly": "System.Data", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteNonQuery", "signature_types": [ - "System.Threading.Tasks.Task`1", - "System.Net.Http.HttpRequestMessage", - "System.Threading.CancellationToken" + "System.Int32" ], "minimum_major": 4, "minimum_minor": 0, @@ -478,21 +343,19 @@ }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.HttpMessageHandlerIntegration", - "method": "HttpMessageHandler_SendAsync", - "signature": "00 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteNonQuery", + "signature": "00 04 08 1C 08 08 0A" } }, { "caller": {}, "target": { - "assembly": "System.Net.Http", - "type": "System.Net.Http.HttpClientHandler", - "method": "SendAsync", + "assembly": "System.Data.Common", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteNonQuery", "signature_types": [ - "System.Threading.Tasks.Task`1", - "System.Net.Http.HttpRequestMessage", - "System.Threading.CancellationToken" + "System.Int32" ], "minimum_major": 4, "minimum_minor": 0, @@ -503,137 +366,171 @@ }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.HttpMessageHandlerIntegration", - "method": "HttpClientHandler_SendAsync", - "signature": "00 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteNonQuery", + "signature": "00 04 08 1C 08 08 0A" } - } - ] - }, - { - "name": "MongoDb", - "method_replacements": [ + }, { "caller": {}, "target": { - "assembly": "MongoDB.Driver.Core", - "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol", - "method": "Execute", + "assembly": "System.Data", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteNonQueryAsync", "signature_types": [ - "System.Void", - "MongoDB.Driver.Core.Connections.IConnection", + "System.Threading.Tasks.Task`1", "System.Threading.CancellationToken" ], - "minimum_major": 2, - "minimum_minor": 2, + "minimum_major": 4, + "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 2, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", - "method": "Execute", - "signature": "00 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteNonQueryAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" } }, { "caller": {}, "target": { - "assembly": "MongoDB.Driver.Core", - "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol`1", - "method": "Execute", + "assembly": "System.Data.Common", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteNonQueryAsync", "signature_types": [ - "T", - "MongoDB.Driver.Core.Connections.IConnection", + "System.Threading.Tasks.Task`1", "System.Threading.CancellationToken" ], - "minimum_major": 2, - "minimum_minor": 2, + "minimum_major": 4, + "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 2, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", - "method": "ExecuteGeneric", - "signature": "00 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteNonQueryAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" } }, { "caller": {}, "target": { - "assembly": "MongoDB.Driver.Core", - "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol", - "method": "ExecuteAsync", + "assembly": "System.Data", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteScalar", "signature_types": [ - "System.Threading.Tasks.Task", - "MongoDB.Driver.Core.Connections.IConnection", + "System.Object" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteScalar", + "signature": "00 04 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.Common", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteScalar", + "signature_types": [ + "System.Object" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteScalar", + "signature": "00 04 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteScalarAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", "System.Threading.CancellationToken" ], - "minimum_major": 2, - "minimum_minor": 1, + "minimum_major": 4, + "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 2, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", - "method": "ExecuteAsync", - "signature": "00 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteScalarAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" } }, { "caller": {}, "target": { - "assembly": "MongoDB.Driver.Core", - "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol`1", - "method": "ExecuteAsync", + "assembly": "System.Data.Common", + "type": "System.Data.Common.DbCommand", + "method": "ExecuteScalarAsync", "signature_types": [ - "System.Threading.Tasks.Task`1", - "MongoDB.Driver.Core.Connections.IConnection", + "System.Threading.Tasks.Task`1", "System.Threading.CancellationToken" ], - "minimum_major": 2, - "minimum_minor": 1, + "minimum_major": 4, + "minimum_minor": 0, "minimum_patch": 0, - "maximum_major": 2, + "maximum_major": 4, "maximum_minor": 65535, "maximum_patch": 65535 }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", - "method": "ExecuteAsyncGeneric", - "signature": "00 06 1C 1C 1C 1C 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.DbCommandIntegration", + "method": "ExecuteScalarAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" } } ] }, { - "name": "ServiceStackRedis", + "name": "ElasticsearchNet5", "method_replacements": [ { "caller": { - "assembly": "ServiceStack.Redis" + "assembly": "Elasticsearch.Net" }, "target": { - "assembly": "ServiceStack.Redis", - "type": "ServiceStack.Redis.RedisNativeClient", - "method": "SendReceive", + "assembly": "Elasticsearch.Net", + "type": "Elasticsearch.Net.IRequestPipeline", + "method": "CallElasticsearch", "signature_types": [ - "T", - "System.Byte[][]", - "System.Func`1", - "System.Action`1>", - "System.Boolean" + "Elasticsearch.Net.ElasticsearchResponse`1", + "Elasticsearch.Net.RequestData" ], - "minimum_major": 4, + "minimum_major": 5, "minimum_minor": 0, "minimum_patch": 0, "maximum_major": 5, @@ -642,9 +539,683 @@ }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.ServiceStackRedisIntegration", - "method": "SendReceive", - "signature": "10 01 08 1E 00 1C 1D 1D 05 1C 1C 02 08 08 0A" + "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet5Integration", + "method": "CallElasticsearch", + "signature": "10 01 05 1C 1C 1C 08 08 0A" + } + }, + { + "caller": { + "assembly": "Elasticsearch.Net" + }, + "target": { + "assembly": "Elasticsearch.Net", + "type": "Elasticsearch.Net.IRequestPipeline", + "method": "CallElasticsearchAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1>", + "Elasticsearch.Net.RequestData", + "System.Threading.CancellationToken" + ], + "minimum_major": 5, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 5, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet5Integration", + "method": "CallElasticsearchAsync", + "signature": "10 01 06 1C 1C 1C 1C 08 08 0A" + } + } + ] + }, + { + "name": "ElasticsearchNet6", + "method_replacements": [ + { + "caller": { + "assembly": "Elasticsearch.Net" + }, + "target": { + "assembly": "Elasticsearch.Net", + "type": "Elasticsearch.Net.IRequestPipeline", + "method": "CallElasticsearch", + "signature_types": [ + "T", + "Elasticsearch.Net.RequestData" + ], + "minimum_major": 6, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 6, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet6Integration", + "method": "CallElasticsearch", + "signature": "10 01 05 1C 1C 1C 08 08 0A" + } + }, + { + "caller": { + "assembly": "Elasticsearch.Net" + }, + "target": { + "assembly": "Elasticsearch.Net", + "type": "Elasticsearch.Net.IRequestPipeline", + "method": "CallElasticsearchAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "Elasticsearch.Net.RequestData", + "System.Threading.CancellationToken" + ], + "minimum_major": 6, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 6, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.ElasticsearchNet6Integration", + "method": "CallElasticsearchAsync", + "signature": "10 01 06 1C 1C 1C 1C 08 08 0A" + } + } + ] + }, + { + "name": "GraphQL", + "method_replacements": [ + { + "caller": {}, + "target": { + "assembly": "GraphQL", + "type": "GraphQL.Validation.IDocumentValidator", + "method": "Validate", + "signature_types": [ + "GraphQL.Validation.IValidationResult", + "System.String", + "GraphQL.Types.ISchema", + "GraphQL.Language.AST.Document", + "System.Collections.Generic.IEnumerable`1", + "_", + "GraphQL.Inputs" + ], + "minimum_major": 2, + "minimum_minor": 3, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.GraphQLIntegration", + "method": "Validate", + "signature": "00 0A 1C 1C 1C 1C 1C 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "GraphQL", + "type": "GraphQL.Execution.IExecutionStrategy", + "method": "ExecuteAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "GraphQL.Execution.ExecutionContext" + ], + "minimum_major": 2, + "minimum_minor": 3, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.GraphQLIntegration", + "method": "ExecuteAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" + } + } + ] + }, + { + "name": "HttpMessageHandler", + "method_replacements": [ + { + "caller": {}, + "target": { + "assembly": "System.Net.Http", + "type": "System.Net.Http.HttpMessageHandler", + "method": "SendAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Net.Http.HttpRequestMessage", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.HttpMessageHandlerIntegration", + "method": "HttpMessageHandler_SendAsync", + "signature": "00 06 1C 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Net.Http", + "type": "System.Net.Http.HttpClientHandler", + "method": "SendAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Net.Http.HttpRequestMessage", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.HttpMessageHandlerIntegration", + "method": "HttpClientHandler_SendAsync", + "signature": "00 06 1C 1C 1C 1C 08 08 0A" + } + } + ] + }, + { + "name": "MongoDb", + "method_replacements": [ + { + "caller": {}, + "target": { + "assembly": "MongoDB.Driver.Core", + "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol", + "method": "Execute", + "signature_types": [ + "System.Void", + "MongoDB.Driver.Core.Connections.IConnection", + "System.Threading.CancellationToken" + ], + "minimum_major": 2, + "minimum_minor": 2, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", + "method": "Execute", + "signature": "00 06 1C 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "MongoDB.Driver.Core", + "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol`1", + "method": "Execute", + "signature_types": [ + "T", + "MongoDB.Driver.Core.Connections.IConnection", + "System.Threading.CancellationToken" + ], + "minimum_major": 2, + "minimum_minor": 2, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", + "method": "ExecuteGeneric", + "signature": "00 06 1C 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "MongoDB.Driver.Core", + "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol", + "method": "ExecuteAsync", + "signature_types": [ + "System.Threading.Tasks.Task", + "MongoDB.Driver.Core.Connections.IConnection", + "System.Threading.CancellationToken" + ], + "minimum_major": 2, + "minimum_minor": 1, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", + "method": "ExecuteAsync", + "signature": "00 06 1C 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "MongoDB.Driver.Core", + "type": "MongoDB.Driver.Core.WireProtocol.IWireProtocol`1", + "method": "ExecuteAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "MongoDB.Driver.Core.Connections.IConnection", + "System.Threading.CancellationToken" + ], + "minimum_major": 2, + "minimum_minor": 1, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.MongoDbIntegration", + "method": "ExecuteAsyncGeneric", + "signature": "00 06 1C 1C 1C 1C 08 08 0A" + } + } + ] + }, + { + "name": "ServiceStackRedis", + "method_replacements": [ + { + "caller": { + "assembly": "ServiceStack.Redis" + }, + "target": { + "assembly": "ServiceStack.Redis", + "type": "ServiceStack.Redis.RedisNativeClient", + "method": "SendReceive", + "signature_types": [ + "T", + "System.Byte[][]", + "System.Func`1", + "System.Action`1>", + "System.Boolean" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 5, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.ServiceStackRedisIntegration", + "method": "SendReceive", + "signature": "10 01 08 1E 00 1C 1D 1D 05 1C 1C 02 08 08 0A" + } + } + ] + }, + { + "name": "SqlCommand", + "method_replacements": [ + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteReader", + "signature_types": [ + "System.Data.SqlClient.SqlDataReader" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteReader", + "signature": "00 04 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.SqlClient", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteReader", + "signature_types": [ + "System.Data.SqlClient.SqlDataReader" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteReader", + "signature": "00 04 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteReader", + "signature_types": [ + "System.Data.SqlClient.SqlDataReader", + "System.Data.CommandBehavior" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteReaderWithBehavior", + "signature": "00 05 1C 1C 08 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.SqlClient", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteReader", + "signature_types": [ + "System.Data.SqlClient.SqlDataReader", + "System.Data.CommandBehavior" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteReaderWithBehavior", + "signature": "00 05 1C 1C 08 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteReaderAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Data.CommandBehavior", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteReaderAsync", + "signature": "00 06 1C 1C 08 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.SqlClient", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteReaderAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Data.CommandBehavior", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteReaderAsync", + "signature": "00 06 1C 1C 08 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteNonQuery", + "signature_types": [ + "System.Int32" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteNonQuery", + "signature": "00 04 08 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.SqlClient", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteNonQuery", + "signature_types": [ + "System.Int32" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteNonQuery", + "signature": "00 04 08 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteNonQueryAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteNonQueryAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.SqlClient", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteNonQueryAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteNonQueryAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteScalar", + "signature_types": [ + "System.Object" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteScalar", + "signature": "00 04 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.SqlClient", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteScalar", + "signature_types": [ + "System.Object" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteScalar", + "signature": "00 04 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteScalarAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteScalarAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" + } + }, + { + "caller": {}, + "target": { + "assembly": "System.Data.SqlClient", + "type": "System.Data.SqlClient.SqlCommand", + "method": "ExecuteScalarAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1", + "System.Threading.CancellationToken" + ], + "minimum_major": 4, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 4, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=1.7.0.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.AdoNet.SqlCommandIntegration", + "method": "ExecuteScalarAsync", + "signature": "00 05 1C 1C 1C 08 08 0A" } } ] diff --git a/sample-libs/Samples.DatabaseHelper/Extensions.cs b/sample-libs/Samples.DatabaseHelper/Extensions.cs new file mode 100644 index 000000000000..1f1f145e69b6 --- /dev/null +++ b/sample-libs/Samples.DatabaseHelper/Extensions.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Data; +using System.Data.Common; + +namespace Samples.DatabaseHelper +{ + public static class Extensions + { + public static IEnumerable AsDataRecords(this DbDataReader reader) + { + while (reader.Read()) + { + yield return reader; + } + } + + public static DbParameter CreateParameterWithValue(this DbCommand command, string name, object value) + { + var parameter = command.CreateParameter(); + parameter.ParameterName = name; + parameter.Value = value; + return parameter; + } + + public static DbParameter AddParameterWithValue(this DbCommand command, string name, object value) + { + DbParameter parameter = CreateParameterWithValue(command, name, value); + command.Parameters.Add(parameter); + return parameter; + } + } +} diff --git a/sample-libs/Samples.DatabaseHelper/RelationalDatabaseTestHarness.cs b/sample-libs/Samples.DatabaseHelper/RelationalDatabaseTestHarness.cs new file mode 100644 index 000000000000..83bd9a6eb403 --- /dev/null +++ b/sample-libs/Samples.DatabaseHelper/RelationalDatabaseTestHarness.cs @@ -0,0 +1,286 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; +using Datadog.Trace; + +namespace Samples.DatabaseHelper +{ + public class RelationalDatabaseTestHarness + where TConnection : DbConnection + where TCommand : DbCommand + where TDataReader : DbDataReader + { + private const string DropCommandText = "DROP TABLE IF EXISTS Employees; CREATE TABLE Employees (Id int PRIMARY KEY, Name varchar(100));"; + private const string InsertCommandText = "INSERT INTO Employees (Id, Name) VALUES (@Id, @Name);"; + private const string SelectOneCommandText = "SELECT Name FROM Employees WHERE Id=@Id;"; + private const string UpdateCommandText = "UPDATE Employees SET Name=@Name WHERE Id=@Id;"; + private const string SelectManyCommandText = "SELECT * FROM Employees WHERE Id=@Id;"; + private const string DeleteCommandText = "DELETE FROM Employees WHERE Id=@Id;"; + + private readonly TConnection _connection; + + private readonly Func _executeNonQuery; + private readonly Func _executeScalar; + private readonly Func _executeReader; + private readonly Func _executeReaderWithBehavior; + + private readonly Func> _executeNonQueryAsync; + private readonly Func> _executeScalarAsync; + private readonly Func> _executeReaderAsync; + private readonly Func> _executeReaderWithBehaviorAsync; + + public RelationalDatabaseTestHarness( + TConnection connection, + Func executeNonQuery, + Func executeScalar, + Func executeReader, + Func executeReaderWithBehavior, + Func> executeNonQueryAsync, + Func> executeScalarAsync, + Func> executeReaderAsync, + Func> executeReaderWithBehaviorAsync) + { + _connection = connection ?? throw new ArgumentNullException(nameof(connection)); + + _executeNonQuery = executeNonQuery ?? throw new ArgumentNullException(nameof(executeNonQuery)); + _executeScalar = executeScalar ?? throw new ArgumentNullException(nameof(executeScalar)); + _executeReader = executeReader ?? throw new ArgumentNullException(nameof(executeReader)); + _executeReaderWithBehavior = executeReaderWithBehavior ?? throw new ArgumentNullException(nameof(executeReaderWithBehavior)); + + _executeNonQueryAsync = executeNonQueryAsync ?? throw new ArgumentNullException(nameof(executeNonQueryAsync)); + _executeScalarAsync = executeScalarAsync ?? throw new ArgumentNullException(nameof(executeScalarAsync)); + + // these two are not implemented by all ADO.NET providers, so they can be null + _executeReaderAsync = executeReaderAsync; + _executeReaderWithBehaviorAsync = executeReaderWithBehaviorAsync; + } + + public async Task RunAsync() + { + using (var scopeAll = Tracer.Instance.StartActive("run.all")) + { + scopeAll.Span.SetTag("command-type", typeof(TCommand).FullName); + + using (var scopeSync = Tracer.Instance.StartActive("run.sync")) + { + scopeSync.Span.SetTag("command-type", typeof(TCommand).FullName); + + _connection.Open(); + CreateNewTable(_connection); + InsertRow(_connection); + SelectScalar(_connection); + UpdateRow(_connection); + SelectRecords(_connection); + DeleteRecord(_connection); + _connection.Close(); + } + + // leave a small space between spans, for better visibility in the UI + await Task.Delay(TimeSpan.FromSeconds(0.1)); + + using (var scopeAsync = Tracer.Instance.StartActive("run.async")) + { + scopeAsync.Span.SetTag("command-type", typeof(TCommand).FullName); + + await _connection.OpenAsync(); + await CreateNewTableAsync(_connection); + await InsertRowAsync(_connection); + await SelectScalarAsync(_connection); + await UpdateRowAsync(_connection); + await SelectRecordsAsync(_connection); + await DeleteRecordAsync(_connection); + _connection.Close(); + } + } + } + + private void DeleteRecord(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = DeleteCommandText; + command.AddParameterWithValue("Id", 1); + + int records = _executeNonQuery(command); + Console.WriteLine($"Deleted {records} record(s)."); + } + } + + private void SelectRecords(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = SelectManyCommandText; + command.AddParameterWithValue("Id", 1); + + using (var reader = _executeReader(command)) + { + var employees = reader.AsDataRecords() + .Select( + r => new { Id = (int)r["Id"], Name = (string)r["Name"] }) + .ToList(); + + Console.WriteLine($"Selected {employees.Count} record(s)."); + } + + using (var reader = _executeReaderWithBehavior(command, CommandBehavior.Default)) + { + var employees = reader.AsDataRecords() + .Select( + r => new { Id = (int)r["Id"], Name = (string)r["Name"] }) + .ToList(); + + Console.WriteLine($"Selected {employees.Count} record(s) with `CommandBehavior.Default`."); + } + } + } + + private void UpdateRow(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = UpdateCommandText; + command.AddParameterWithValue("Name", "Name2"); + command.AddParameterWithValue("Id", 1); + + int records = _executeNonQuery(command); + Console.WriteLine($"Updated {records} record(s)."); + } + } + + private void SelectScalar(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = SelectOneCommandText; + command.AddParameterWithValue("Id", 1); + + var name = _executeScalar(command) as string; + Console.WriteLine($"Selected scalar `{name ?? "(null)"}`."); + } + } + + private void InsertRow(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = InsertCommandText; + command.AddParameterWithValue("Id", 1); + command.AddParameterWithValue("Name", "Name1"); + + int records = _executeNonQuery(command); + Console.WriteLine($"Inserted {records} record(s)."); + } + } + + private void CreateNewTable(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = DropCommandText; + + int records = _executeNonQuery(command); + Console.WriteLine($"Dropped and recreated table. {records} record(s) affected."); + } + } + + private async Task DeleteRecordAsync(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = DeleteCommandText; + command.AddParameterWithValue("Id", 1); + + int records = await _executeNonQueryAsync(command); + Console.WriteLine($"Deleted {records} record(s)."); + } + } + + private async Task SelectRecordsAsync(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = SelectManyCommandText; + command.AddParameterWithValue("Id", 1); + + if (_executeReaderAsync != null) + { + using (var reader = await _executeReaderAsync(command)) + { + var employees = reader.AsDataRecords() + .Select( + r => new { Id = (int)r["Id"], Name = (string)r["Name"] }) + .ToList(); + + Console.WriteLine($"Selected {employees.Count} record(s)."); + } + } + + if (_executeReaderWithBehaviorAsync != null) + { + using (var reader = await _executeReaderWithBehaviorAsync(command, CommandBehavior.Default)) + { + var employees = reader.AsDataRecords() + .Select( + r => new { Id = (int)r["Id"], Name = (string)r["Name"] }) + .ToList(); + + Console.WriteLine($"Selected {employees.Count} record(s) with `CommandBehavior.Default`."); + } + } + } + } + + private async Task UpdateRowAsync(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = UpdateCommandText; + command.AddParameterWithValue("Name", "Name2"); + command.AddParameterWithValue("Id", 1); + + int records = await _executeNonQueryAsync(command); + Console.WriteLine($"Updated {records} record(s)."); + } + } + + private async Task SelectScalarAsync(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = SelectOneCommandText; + command.AddParameterWithValue("Id", 1); + + object nameObj = await _executeScalarAsync(command); + var name = nameObj as string; + Console.WriteLine($"Selected scalar `{name ?? "(null)"}`."); + } + } + + private async Task InsertRowAsync(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = InsertCommandText; + command.AddParameterWithValue("Id", 1); + command.AddParameterWithValue("Name", "Name1"); + + int records = await _executeNonQueryAsync(command); + Console.WriteLine($"Inserted {records} record(s)."); + } + } + + private async Task CreateNewTableAsync(DbConnection connection) + { + using (var command = (TCommand)connection.CreateCommand()) + { + command.CommandText = DropCommandText; + + int records = await _executeNonQueryAsync(command); + Console.WriteLine($"Dropped and recreated table. {records} record(s) affected."); + } + } + } +} diff --git a/sample-libs/Samples.DatabaseHelper/Samples.DatabaseHelper.csproj b/sample-libs/Samples.DatabaseHelper/Samples.DatabaseHelper.csproj new file mode 100644 index 000000000000..587a93841c14 --- /dev/null +++ b/sample-libs/Samples.DatabaseHelper/Samples.DatabaseHelper.csproj @@ -0,0 +1,12 @@ + + + + net45;netstandard2.0 + netstandard2.0 + + + + + + + diff --git a/samples/Samples.MySql/Program.cs b/samples/Samples.MySql/Program.cs index 995f86e20408..efb250712437 100644 --- a/samples/Samples.MySql/Program.cs +++ b/samples/Samples.MySql/Program.cs @@ -1,83 +1,64 @@ using System; -using Datadog.Trace.ClrProfiler; +using System.Data.Common; +using System.Threading.Tasks; using MySql.Data.MySqlClient; +using Samples.DatabaseHelper; namespace Samples.MySql { - class Program + internal static class Program { - private static string Host() + private static async Task Main() { - return Environment.GetEnvironmentVariable("MYSQL_HOST") ?? "localhost"; - } - - private static string ConnectionString(string database) - { - return $"server={Host()};user=mysqldb;password=mysqldb;port=3306;database={database}"; - } - - private static void Main(string[] args) - { - Console.WriteLine($"Profiler attached: {Instrumentation.ProfilerAttached}"); - Console.WriteLine($"Platform: {(Environment.Is64BitProcess ? "x64" : "x32")}"); - Console.WriteLine(); - - Console.WriteLine("Opening the connection."); + /* TODO: enable this after adding a MySql-specific integration + using (var connection = CreateConnection()) + { + var testQueries = new RelationalDatabaseTestHarness( + connection, + command => command.ExecuteNonQuery(), + command => command.ExecuteScalar(), + command => command.ExecuteReader(), + (command, behavior) => command.ExecuteReader(behavior), + command => command.ExecuteNonQueryAsync(), + command => command.ExecuteScalarAsync(), + executeReaderAsync: null, + executeReaderWithBehaviorAsync: null + ); - string connStr = ConnectionString("world"); - var conn = new MySqlConnection(connStr); - conn.Open(); - Console.WriteLine("Creating the table for the continents."); + await testQueries.RunAsync(); + } + */ - // Create table - var tableCommand = - new MySqlCommand( - "DROP TABLE IF EXISTS `continent`; CREATE TABLE continent (continent_id INT AUTO_INCREMENT, name VARCHAR(255) NOT NULL, PRIMARY KEY(continent_id));", - conn); - tableCommand.ExecuteNonQuery(); + using (var connection = CreateConnection()) + { + var testQueries = new RelationalDatabaseTestHarness( + connection, + command => command.ExecuteNonQuery(), + command => command.ExecuteScalar(), + command => command.ExecuteReader(), + (command, behavior) => command.ExecuteReader(behavior), + command => command.ExecuteNonQueryAsync(), + command => command.ExecuteScalarAsync(), + command => command.ExecuteReaderAsync(), + (command, behavior) => command.ExecuteReaderAsync(behavior) + ); - Console.WriteLine("Creating the continents."); + await testQueries.RunAsync(); + } + } - // Create continents - MySqlCommand createContinent; - createContinent = new MySqlCommand("INSERT INTO continent (name) VALUES ('Africa');", conn); - createContinent.ExecuteNonQuery(); - createContinent = new MySqlCommand("INSERT INTO continent (name) VALUES ('Antarctica');", conn); - createContinent.ExecuteNonQuery(); - createContinent = new MySqlCommand("INSERT INTO continent (name) VALUES ('Asia');", conn); - createContinent.ExecuteNonQuery(); - createContinent = new MySqlCommand("INSERT INTO continent (name) VALUES ('Australia');", conn); - createContinent.ExecuteNonQuery(); - createContinent = new MySqlCommand("INSERT INTO continent (name) VALUES ('Europe');", conn); - createContinent.ExecuteNonQuery(); - createContinent = new MySqlCommand("INSERT INTO continent (name) VALUES ('North America');", conn); - createContinent.ExecuteNonQuery(); - createContinent = new MySqlCommand("INSERT INTO continent (name) VALUES ('South America');", conn); - createContinent.ExecuteNonQuery(); + private static MySqlConnection CreateConnection() + { + var connectionString = Environment.GetEnvironmentVariable("MYSQL_CONNECTION_STRING"); - try - { - Console.WriteLine("Beginning to read the continents."); - string sql = "SELECT continent_id, name FROM continent"; - var readContinents = new MySqlCommand(sql, conn); - using (var rdr = readContinents.ExecuteReader()) - { - while (rdr.Read()) - { - Console.WriteLine(rdr[0] + " -- " + rdr[1]); - } - rdr.Close(); - } - Console.WriteLine("Done reading the continents."); - } - catch (Exception ex) + if (connectionString == null) { - Console.WriteLine(ex.ToString()); + var host = Environment.GetEnvironmentVariable("MYSQL_HOST") ?? "localhost"; + connectionString = $"server={host};user=mysqldb;password=mysqldb;port=3306;database=world"; } - conn.Close(); - Console.WriteLine("Done setting up, inserting, and reading from MySQL."); + return new MySqlConnection(connectionString); } } } diff --git a/samples/Samples.MySql/Samples.MySql.csproj b/samples/Samples.MySql/Samples.MySql.csproj index 3a0fef0e4257..41dac3365fd4 100644 --- a/samples/Samples.MySql/Samples.MySql.csproj +++ b/samples/Samples.MySql/Samples.MySql.csproj @@ -10,4 +10,8 @@ + + + + \ No newline at end of file diff --git a/samples/Samples.Npgsql/Program.cs b/samples/Samples.Npgsql/Program.cs index eb36fefc50c3..429784597a3e 100644 --- a/samples/Samples.Npgsql/Program.cs +++ b/samples/Samples.Npgsql/Program.cs @@ -1,103 +1,64 @@ using System; -using System.Linq; -using Datadog.Trace.ClrProfiler; +using System.Data.Common; +using System.Threading.Tasks; using Npgsql; +using Samples.DatabaseHelper; namespace Samples.Npgsql { - public static class Program + internal static class Program { - private static string Host() + private static async Task Main() { - return Environment.GetEnvironmentVariable("POSTGRES_HOST") ?? "localhost"; - } + /* TODO: enable this after adding a Npgsql-specific integration + using (var connection = CreateConnection()) + { + var testQueries = new RelationalDatabaseTestHarness( + connection, + command => command.ExecuteNonQuery(), + command => command.ExecuteScalar(), + command => command.ExecuteReader(), + (command, behavior) => command.ExecuteReader(behavior), + command => command.ExecuteNonQueryAsync(), + command => command.ExecuteScalarAsync(), + executeReaderAsync: null, + executeReaderWithBehaviorAsync: null + ); + + + await testQueries.RunAsync(); + } + */ - private static string ConnectionString(string database) - { - return $"Host={Host()};Username=postgres;Password=postgres;Database={database}"; + using (var connection = CreateConnection()) + { + var testQueries = new RelationalDatabaseTestHarness( + connection, + command => command.ExecuteNonQuery(), + command => command.ExecuteScalar(), + command => command.ExecuteReader(), + (command, behavior) => command.ExecuteReader(behavior), + command => command.ExecuteNonQueryAsync(), + command => command.ExecuteScalarAsync(), + command => command.ExecuteReaderAsync(), + (command, behavior) => command.ExecuteReaderAsync(behavior) + ); + + await testQueries.RunAsync(); + } } - public static void Main(string[] args) + private static NpgsqlConnection CreateConnection() { - Console.WriteLine($"Profiler attached: {Instrumentation.ProfilerAttached}"); - Console.WriteLine($"Platform: {(Environment.Is64BitProcess ? "x64" : "x32")}"); - Console.WriteLine(); + var connectionString = Environment.GetEnvironmentVariable("POSTGRES_CONNECTION_STRING"); - using (var conn = new NpgsqlConnection(ConnectionString("postgres"))) + if (connectionString == null) { - conn.Open(); - - using (var createTable = conn.CreateCommand()) - { - createTable.CommandText = @" - DROP TABLE IF EXISTS employee; - CREATE TABLE employee ( - employee_id SERIAL, - name varchar(45) NOT NULL, - birth_date varchar(450) NOT NULL, - PRIMARY KEY (employee_id) - )"; - - createTable.ExecuteNonQuery(); - } - - // Insert some data - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = "INSERT INTO employee (name, birth_date) VALUES (@name, @birth_date);"; - cmd.Parameters.AddWithValue("name", "Jane Smith"); - cmd.Parameters.AddWithValue("@birth_date", new DateTime(1980, 2, 3)); - - int count = cmd.ExecuteNonQuery(); - Console.WriteLine($"{count} rows inserted."); - } - - // Retrieve all rows - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = "SELECT * FROM employee sync;"; - int rows = 0; - - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - var values = new object[10]; - int count = reader.GetValues(values); - Console.WriteLine(string.Join(", ", values.Take(count))); - rows++; - } - } - - Console.WriteLine($"{rows:N0} rows returned from sync query."); - Console.WriteLine(); - - cmd.CommandText = "SELECT * FROM employee async;"; - rows = 0; - - using (var reader = cmd.ExecuteReaderAsync().GetAwaiter().GetResult()) - { - while (reader.Read()) - { - var values = new object[10]; - int count = reader.GetValues(values); - Console.WriteLine(string.Join(", ", values.Take(count))); - rows++; - } - } - - Console.WriteLine($"{rows:N0} rows returned from async query."); - } - - // Delete all data - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = "DELETE FROM employee;"; - - int count = cmd.ExecuteNonQuery(); - Console.WriteLine($"{count} rows deleted."); - } + var host = Environment.GetEnvironmentVariable("POSTGRES_HOST") ?? "localhost"; + connectionString = $"Host={host};Username=postgres;Password=postgres;Database=postgres"; } + + return new NpgsqlConnection(connectionString); } } } diff --git a/samples/Samples.Npgsql/Samples.Npgsql.csproj b/samples/Samples.Npgsql/Samples.Npgsql.csproj index ec97bea42dc7..0e690371ba45 100644 --- a/samples/Samples.Npgsql/Samples.Npgsql.csproj +++ b/samples/Samples.Npgsql/Samples.Npgsql.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/samples/Samples.SqlServer/Models.cs b/samples/Samples.SqlServer/Models.cs deleted file mode 100644 index dac6fffafe6c..000000000000 --- a/samples/Samples.SqlServer/Models.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore; - -namespace Samples.SqlServer -{ - public class Blog - { - public int BlogId { get; set; } - public string Name { get; set; } - - public virtual List Posts { get; set; } - } - - public class Post - { - public int PostId { get; set; } - public string Title { get; set; } - public string Content { get; set; } - - public int BlogId { get; set; } - public virtual Blog Blog { get; set; } - } - - public class BloggingContext : DbContext - { - public DbSet Blogs { get; set; } - public DbSet Posts { get; set; } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - // use LocalDB for ease of use - optionsBuilder.UseSqlServer(ConnectionString()); - } - - private static string ConnectionString() - { - return Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING") ?? @"Server=(localdb)\MSSQLLocalDB;Database=BlogDatabase;Integrated Security=true"; - } - } -} diff --git a/samples/Samples.SqlServer/Program.cs b/samples/Samples.SqlServer/Program.cs index e47e0b6c05a8..df26587e8118 100644 --- a/samples/Samples.SqlServer/Program.cs +++ b/samples/Samples.SqlServer/Program.cs @@ -1,60 +1,64 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Data.SqlClient; using System.Linq; -using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using Datadog.Trace; +using Samples.DatabaseHelper; namespace Samples.SqlServer { - class Program + internal static class Program { - static void Main(string[] args) + private static async Task Main() { - using (var db = new BloggingContext()) + using (var root = Tracer.Instance.StartActive("root")) { - // create database if missing - db.Database.EnsureCreated(); - - var name = "test"; - - var blog = (from b in db.Blogs where b.Name == name select b).FirstOrDefault(); - if (blog == null) + using (var connection = CreateConnection()) { - blog = new Blog { Name = name }; - db.Blogs.Add(blog); - db.SaveChanges(); - } + var testQueries = new RelationalDatabaseTestHarness( + connection, + command => command.ExecuteNonQuery(), + command => command.ExecuteScalar(), + command => command.ExecuteReader(), + (command, behavior) => command.ExecuteReader(behavior), + command => command.ExecuteNonQueryAsync(), + command => command.ExecuteScalarAsync(), + command => command.ExecuteReaderAsync(), + (command, behavior) => command.ExecuteReaderAsync(behavior) + ); - // Display all Blogs from the database synchronously - var query = from b in db.Blogs - orderby b.Name - select b; - Console.WriteLine("All blogs in the database from the synchronous call:"); - foreach (var item in query) - { - Console.WriteLine(item.Name); + await testQueries.RunAsync(); } - var asyncName = "test-async"; - - var asyncBlog = (from b in db.Blogs where b.Name == asyncName select b).FirstOrDefaultAsync(); - if (asyncBlog.Result == null) + using (var connection = CreateConnection()) { - blog = new Blog { Name = asyncName }; - db.Blogs.Add(blog); - db.SaveChangesAsync().Wait(); - } - - // Display all Blogs from the database asynchronously - var asyncQueryTask = db.Blogs.Where(b => b.Name == asyncName).ToListAsync(); + var testQueries = new RelationalDatabaseTestHarness( + connection, + command => command.ExecuteNonQuery(), + command => command.ExecuteScalar(), + command => command.ExecuteReader(), + (command, behavior) => command.ExecuteReader(behavior), + command => command.ExecuteNonQueryAsync(), + command => command.ExecuteScalarAsync(), + command => command.ExecuteReaderAsync(), + (command, behavior) => command.ExecuteReaderAsync(behavior) + ); - asyncQueryTask.Wait(); - - Console.WriteLine("All blogs in the database from the async call:"); - foreach (var item in asyncQueryTask.Result) - { - Console.WriteLine(item.Name); + await testQueries.RunAsync(); } } } + + private static SqlConnection CreateConnection() + { + var connectionString = Environment.GetEnvironmentVariable("SQLSERVER_CONNECTION_STRING") ?? + @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;"; + + return new SqlConnection(connectionString); + } } } diff --git a/samples/Samples.SqlServer/Samples.SqlServer.csproj b/samples/Samples.SqlServer/Samples.SqlServer.csproj index edf1a24bf246..37cb64afd707 100644 --- a/samples/Samples.SqlServer/Samples.SqlServer.csproj +++ b/samples/Samples.SqlServer/Samples.SqlServer.csproj @@ -1,19 +1,23 @@  - 2.1.2 - - - net461;netcoreapp2.1;netcoreapp3.0 + 4.7.0 false true + + + + + + + + - - + \ No newline at end of file diff --git a/src/Datadog.Trace.ClrProfiler.Managed/GlobalSuppressions.cs b/src/Datadog.Trace.ClrProfiler.Managed/GlobalSuppressions.cs new file mode 100644 index 000000000000..a0519b8c869f --- /dev/null +++ b/src/Datadog.Trace.ClrProfiler.Managed/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage( + "StyleCop.CSharp.OrderingRules", + "SA1202:Elements must be ordered by access", + Justification = "Allow custom ordering in integrations.")] diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/AdoNetConstants.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/AdoNetConstants.cs new file mode 100644 index 000000000000..906113c4de53 --- /dev/null +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/AdoNetConstants.cs @@ -0,0 +1,30 @@ +namespace Datadog.Trace.ClrProfiler.Integrations.AdoNet +{ + internal static class AdoNetConstants + { + public static class AssemblyNames + { + // .NET Framework + public const string SystemData = "System.Data"; + + // .NET Core + public const string SystemDataCommon = "System.Data.Common"; + public const string SystemDataSqlClient = "System.Data.SqlClient"; + } + + public static class TypeNames + { + public const string CommandBehavior = "System.Data.CommandBehavior"; + } + + public static class MethodNames + { + public const string ExecuteNonQuery = "ExecuteNonQuery"; + public const string ExecuteNonQueryAsync = "ExecuteNonQueryAsync"; + public const string ExecuteScalar = "ExecuteScalar"; + public const string ExecuteScalarAsync = "ExecuteScalarAsync"; + public const string ExecuteReader = "ExecuteReader"; + public const string ExecuteReaderAsync = "ExecuteReaderAsync"; + } + } +} diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/DbCommandIntegration.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/DbCommandIntegration.cs new file mode 100644 index 000000000000..28a63ee412c7 --- /dev/null +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/DbCommandIntegration.cs @@ -0,0 +1,484 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.Emit; +using Datadog.Trace.Logging; + +namespace Datadog.Trace.ClrProfiler.Integrations.AdoNet +{ + /// + /// Instrumentation wrappers for . + /// + public static class DbCommandIntegration + { + // TODO: support both "DbCommand" (new name) and + // "AdoNet" (backwards compatibility) when reading configuration settings + private const string IntegrationName = "AdoNet"; + private const string Major4 = "4"; + + private const string DbCommandTypeName = "System.Data.Common.DbCommand"; + private const string DbDataReaderTypeName = "System.Data.Common.DbDataReader"; + + private static readonly Vendors.Serilog.ILogger Log = DatadogLogging.GetLogger(typeof(DbCommandIntegration)); + + /// + /// Instrumentation wrapper for . + /// + /// The object referenced "this" in the instrumented method. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataCommon }, + TargetType = DbCommandTypeName, + TargetSignatureTypes = new[] { DbDataReaderTypeName }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteReader( + object command, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + + try + { + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteReader) + .WithConcreteType(typeof(DbCommand)) + .WithNamespaceAndNameFilters(DbDataReaderTypeName) + .Build(); + } + catch (Exception ex) + { + Log.ErrorRetrievingMethod( + exception: ex, + moduleVersionPointer: moduleVersionPtr, + mdToken: mdToken, + opCode: opCode, + instrumentedType: DbCommandTypeName, + methodName: nameof(ExecuteReader), + instanceType: command.GetType().AssemblyQualifiedName); + throw; + } + + var dbCommand = command as DbCommand; + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, dbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(dbCommand); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for . + /// + /// The object referenced "this" in the instrumented method. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataCommon }, + TargetMethod = AdoNetConstants.MethodNames.ExecuteReader, + TargetType = DbCommandTypeName, + TargetSignatureTypes = new[] { DbDataReaderTypeName, AdoNetConstants.TypeNames.CommandBehavior }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteReaderWithBehavior( + object command, + int behavior, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + var commandBehavior = (CommandBehavior)behavior; + + try + { + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteReader) + .WithConcreteType(typeof(DbCommand)) + .WithParameters(commandBehavior) + .WithNamespaceAndNameFilters(DbDataReaderTypeName, AdoNetConstants.TypeNames.CommandBehavior) + .Build(); + } + catch (Exception ex) + { + Log.ErrorRetrievingMethod( + exception: ex, + moduleVersionPointer: moduleVersionPtr, + mdToken: mdToken, + opCode: opCode, + instrumentedType: DbCommandTypeName, + methodName: nameof(ExecuteReader), + instanceType: command.GetType().AssemblyQualifiedName); + throw; + } + + var dbCommand = command as DbCommand; + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, dbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(dbCommand, commandBehavior); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for DbCommand.ExecuteReaderAsync(). + /// + /// The object referenced by this in the instrumented method. + /// The value used in the original method call. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataCommon }, + TargetType = DbCommandTypeName, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", AdoNetConstants.TypeNames.CommandBehavior, ClrNames.CancellationToken }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteReaderAsync( + object command, + int behavior, + object cancellationTokenSource, + int opCode, + int mdToken, + long moduleVersionPtr) + { + var tokenSource = cancellationTokenSource as CancellationTokenSource; + var cancellationToken = tokenSource?.Token ?? CancellationToken.None; + + return ExecuteReaderAsyncInternal( + command as DbCommand, + (CommandBehavior)behavior, + cancellationToken, + opCode, + mdToken, + moduleVersionPtr); + } + + private static async Task ExecuteReaderAsyncInternal( + DbCommand command, + CommandBehavior commandBehavior, + CancellationToken cancellationToken, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func> instrumentedMethod; + + try + { + instrumentedMethod = + MethodBuilder>> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteReaderAsync) + .WithConcreteType(typeof(DbCommand)) + .WithParameters(commandBehavior, cancellationToken) + .WithNamespaceAndNameFilters(ClrNames.GenericTask, AdoNetConstants.TypeNames.CommandBehavior, ClrNames.CancellationToken) + .Build(); + } + catch (Exception ex) + { + Log.ErrorRetrievingMethod( + exception: ex, + moduleVersionPointer: moduleVersionPtr, + mdToken: mdToken, + opCode: opCode, + instrumentedType: DbCommandTypeName, + methodName: nameof(ExecuteReaderAsync), + instanceType: command.GetType().AssemblyQualifiedName); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command, IntegrationName)) + { + try + { + return await instrumentedMethod(command, commandBehavior, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for DbCommand.ExecuteNonQuery(). + /// + /// The object referenced by this in the instrumented method. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataCommon }, + TargetType = DbCommandTypeName, + TargetSignatureTypes = new[] { ClrNames.Int32 }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static int ExecuteNonQuery( + object command, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + + try + { + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteNonQuery) + .WithConcreteType(typeof(DbCommand)) + .WithNamespaceAndNameFilters(ClrNames.Int32) + .Build(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error resolving {DbCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteNonQuery}(...)"); + throw; + } + + var dbCommand = command as DbCommand; + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, dbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(dbCommand); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for DbCommand.ExecuteNonQueryAsync(). + /// + /// The object referenced by this in the instrumented method. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataCommon }, + TargetType = DbCommandTypeName, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", ClrNames.CancellationToken }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteNonQueryAsync( + object command, + object cancellationTokenSource, + int opCode, + int mdToken, + long moduleVersionPtr) + { + var tokenSource = cancellationTokenSource as CancellationTokenSource; + var cancellationToken = tokenSource?.Token ?? CancellationToken.None; + + return ExecuteNonQueryAsyncInternal( + command as DbCommand, + cancellationToken, + opCode, + mdToken, + moduleVersionPtr); + } + + private static async Task ExecuteNonQueryAsyncInternal( + DbCommand command, + CancellationToken cancellationToken, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func> instrumentedMethod; + + try + { + instrumentedMethod = + MethodBuilder>> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteNonQueryAsync) + .WithConcreteType(typeof(DbCommand)) + .WithParameters(cancellationToken) + .WithNamespaceAndNameFilters(ClrNames.GenericTask, ClrNames.CancellationToken) + .Build(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error resolving {DbCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteNonQueryAsync}(...)"); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command, IntegrationName)) + { + try + { + return await instrumentedMethod(command, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for DbCommand.ExecuteScalar(). + /// + /// The object referenced by this in the instrumented method. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataCommon }, + TargetType = DbCommandTypeName, + TargetSignatureTypes = new[] { ClrNames.Object }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteScalar( + object command, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + + try + { + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteScalar) + .WithConcreteType(typeof(DbCommand)) + .WithNamespaceAndNameFilters(ClrNames.Object) + .Build(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error resolving {DbCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteScalar}(...)"); + throw; + } + + var dbCommand = command as DbCommand; + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, dbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(dbCommand); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for DbCommand.ExecuteScalarAsync(). + /// + /// The object referenced by this in the instrumented method. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataCommon }, + TargetType = DbCommandTypeName, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", ClrNames.CancellationToken }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteScalarAsync( + object command, + object cancellationTokenSource, + int opCode, + int mdToken, + long moduleVersionPtr) + { + var tokenSource = cancellationTokenSource as CancellationTokenSource; + var cancellationToken = tokenSource?.Token ?? CancellationToken.None; + + return ExecuteScalarAsyncInternal( + command as DbCommand, + cancellationToken, + opCode, + mdToken, + moduleVersionPtr); + } + + private static async Task ExecuteScalarAsyncInternal( + DbCommand command, + CancellationToken cancellationToken, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func> instrumentedMethod; + + try + { + instrumentedMethod = + MethodBuilder>> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteScalarAsync) + .WithConcreteType(typeof(DbCommand)) + .WithParameters(cancellationToken) + .WithNamespaceAndNameFilters(ClrNames.GenericTask, ClrNames.CancellationToken) + .Build(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error resolving {DbCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteScalarAsync}(...)"); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command, IntegrationName)) + { + try + { + return await instrumentedMethod(command, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + } +} diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/SqlCommandIntegration.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/SqlCommandIntegration.cs new file mode 100644 index 000000000000..dbd57a4afa39 --- /dev/null +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNet/SqlCommandIntegration.cs @@ -0,0 +1,472 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.Emit; +using Datadog.Trace.Logging; + +namespace Datadog.Trace.ClrProfiler.Integrations.AdoNet +{ + /// + /// Instrumentation wrappers for SqlCommand. + /// + public static class SqlCommandIntegration + { + private const string IntegrationName = "SqlCommand"; + private const string Major4 = "4"; + + private const string SqlCommandTypeName = "System.Data.SqlClient.SqlCommand"; + private const string SqlDataReaderTypeName = "System.Data.SqlClient.SqlDataReader"; + + private static readonly ILog Log = LogProvider.GetCurrentClassLogger(); + + /// + /// Instrumentation wrapper for SqlCommand.ExecuteReader(). + /// + /// The object referenced by this in the instrumented method. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataSqlClient }, + TargetType = SqlCommandTypeName, + TargetMethod = AdoNetConstants.MethodNames.ExecuteReader, + TargetSignatureTypes = new[] { SqlDataReaderTypeName }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteReader( + object command, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + + try + { + var targetType = command.GetInstrumentedType(SqlCommandTypeName); + + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteReader) + .WithConcreteType(targetType) + .WithNamespaceAndNameFilters(SqlDataReaderTypeName) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {SqlCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteReader}(...)", ex); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command as DbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(command); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for SqlCommand.ExecuteReader(). + /// + /// The object referenced by this in the instrumented method. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataSqlClient }, + TargetType = SqlCommandTypeName, + TargetMethod = AdoNetConstants.MethodNames.ExecuteReader, + TargetSignatureTypes = new[] { SqlDataReaderTypeName, AdoNetConstants.TypeNames.CommandBehavior }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteReaderWithBehavior( + object command, + int behavior, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + var commandBehavior = (CommandBehavior)behavior; + + try + { + var targetType = command.GetInstrumentedType(SqlCommandTypeName); + + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteReader) + .WithConcreteType(targetType) + .WithParameters(commandBehavior) + .WithNamespaceAndNameFilters(SqlDataReaderTypeName, AdoNetConstants.TypeNames.CommandBehavior) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {SqlCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteReader}(...)", ex); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command as DbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(command, commandBehavior); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for SqlCommand.ExecuteReaderAsync(). + /// + /// The object referenced by this in the instrumented method. + /// The value used in the original method call. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataSqlClient }, + TargetType = SqlCommandTypeName, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", AdoNetConstants.TypeNames.CommandBehavior, ClrNames.CancellationToken }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteReaderAsync( + object command, + int behavior, + object cancellationTokenSource, + int opCode, + int mdToken, + long moduleVersionPtr) + { + var tokenSource = cancellationTokenSource as CancellationTokenSource; + var cancellationToken = tokenSource?.Token ?? CancellationToken.None; + + return ExecuteReaderAsyncInternal( + (DbCommand)command, + (CommandBehavior)behavior, + cancellationToken, + opCode, + mdToken, + moduleVersionPtr); + } + + private static async Task ExecuteReaderAsyncInternal( + DbCommand command, + CommandBehavior commandBehavior, + CancellationToken cancellationToken, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func> instrumentedMethod; + + try + { + var targetType = command.GetInstrumentedType(SqlCommandTypeName); + + instrumentedMethod = + MethodBuilder>> + .Start(moduleVersionPtr, mdToken, opCode, nameof(ExecuteReaderAsync)) + .WithConcreteType(targetType) + .WithParameters(commandBehavior, cancellationToken) + .WithNamespaceAndNameFilters(ClrNames.GenericTask, AdoNetConstants.TypeNames.CommandBehavior, ClrNames.CancellationToken) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {SqlCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteReaderAsync}(...)", ex); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command, IntegrationName)) + { + try + { + return await instrumentedMethod(command, commandBehavior, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for SqlCommand.ExecuteNonQuery(). + /// + /// The object referenced by this in the instrumented method. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataSqlClient }, + TargetType = SqlCommandTypeName, + TargetSignatureTypes = new[] { ClrNames.Int32 }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static int ExecuteNonQuery( + object command, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + + try + { + var targetType = command.GetInstrumentedType(SqlCommandTypeName); + + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteNonQuery) + .WithConcreteType(targetType) + .WithNamespaceAndNameFilters(ClrNames.Int32) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {SqlCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteNonQuery}(...)", ex); + throw; + } + + var dbCommand = command as DbCommand; + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, dbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(dbCommand); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for SqlCommand.ExecuteNonQueryAsync(). + /// + /// The object referenced by this in the instrumented method. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataSqlClient }, + TargetType = SqlCommandTypeName, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", ClrNames.CancellationToken }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteNonQueryAsync( + object command, + object cancellationTokenSource, + int opCode, + int mdToken, + long moduleVersionPtr) + { + var tokenSource = cancellationTokenSource as CancellationTokenSource; + var cancellationToken = tokenSource?.Token ?? CancellationToken.None; + + return ExecuteNonQueryAsyncInternal( + command as DbCommand, + cancellationToken, + opCode, + mdToken, + moduleVersionPtr); + } + + private static async Task ExecuteNonQueryAsyncInternal( + DbCommand command, + CancellationToken cancellationToken, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func> instrumentedMethod; + + try + { + var targetType = command.GetInstrumentedType(SqlCommandTypeName); + + instrumentedMethod = + MethodBuilder>> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteNonQueryAsync) + .WithConcreteType(targetType) + .WithParameters(cancellationToken) + .WithNamespaceAndNameFilters(ClrNames.GenericTask, ClrNames.CancellationToken) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {SqlCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteNonQueryAsync}(...)", ex); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command, IntegrationName)) + { + try + { + return await instrumentedMethod(command, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for SqlCommand.ExecuteScalar(). + /// + /// The object referenced by this in the instrumented method. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataSqlClient }, + TargetType = SqlCommandTypeName, + TargetSignatureTypes = new[] { ClrNames.Object }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteScalar( + object command, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func instrumentedMethod; + + try + { + var targetType = command.GetInstrumentedType(SqlCommandTypeName); + + instrumentedMethod = + MethodBuilder> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteScalar) + .WithConcreteType(targetType) + .WithNamespaceAndNameFilters(ClrNames.Object) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {SqlCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteScalar}(...)", ex); + throw; + } + + var dbCommand = command as DbCommand; + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, dbCommand, IntegrationName)) + { + try + { + return instrumentedMethod(dbCommand); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + + /// + /// Instrumentation wrapper for SqlCommand.ExecuteScalarAsync(). + /// + /// The object referenced by this in the instrumented method. + /// The value used in the original method call. + /// The OpCode used in the original method call. + /// The mdToken of the original method call. + /// A pointer to the module version GUID. + /// The value returned by the instrumented method. + [InterceptMethod( + TargetAssemblies = new[] { AdoNetConstants.AssemblyNames.SystemData, AdoNetConstants.AssemblyNames.SystemDataSqlClient }, + TargetType = SqlCommandTypeName, + TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", ClrNames.CancellationToken }, + TargetMinimumVersion = Major4, + TargetMaximumVersion = Major4)] + public static object ExecuteScalarAsync( + object command, + object cancellationTokenSource, + int opCode, + int mdToken, + long moduleVersionPtr) + { + var tokenSource = cancellationTokenSource as CancellationTokenSource; + var cancellationToken = tokenSource?.Token ?? CancellationToken.None; + + return ExecuteScalarAsyncInternal( + command as DbCommand, + cancellationToken, + opCode, + mdToken, + moduleVersionPtr); + } + + private static async Task ExecuteScalarAsyncInternal( + DbCommand command, + CancellationToken cancellationToken, + int opCode, + int mdToken, + long moduleVersionPtr) + { + Func> instrumentedMethod; + + try + { + var targetType = command.GetInstrumentedType(SqlCommandTypeName); + + instrumentedMethod = + MethodBuilder>> + .Start(moduleVersionPtr, mdToken, opCode, AdoNetConstants.MethodNames.ExecuteScalarAsync) + .WithConcreteType(targetType) + .WithParameters(cancellationToken) + .WithNamespaceAndNameFilters(ClrNames.GenericTask, ClrNames.CancellationToken) + .Build(); + } + catch (Exception ex) + { + Log.ErrorException($"Error resolving {SqlCommandTypeName}.{AdoNetConstants.MethodNames.ExecuteScalarAsync}(...)", ex); + throw; + } + + using (var scope = ScopeFactory.CreateDbCommandScope(Tracer.Instance, command, IntegrationName)) + { + try + { + return await instrumentedMethod(command, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + scope?.Span.SetException(ex); + throw; + } + } + } + } +} diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs deleted file mode 100644 index 0bf4dc0cbe60..000000000000 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AdoNetIntegration.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System; -using System.Data; -using System.Data.Common; -using System.Threading; -using System.Threading.Tasks; -using Datadog.Trace.ClrProfiler.Emit; -using Datadog.Trace.ClrProfiler.Helpers; -using Datadog.Trace.ExtensionMethods; -using Datadog.Trace.Logging; - -namespace Datadog.Trace.ClrProfiler.Integrations -{ - /// - /// AdoNetIntegration provides methods that add tracing to ADO.NET calls. - /// - public static class AdoNetIntegration - { - private const string IntegrationName = "AdoNet"; - private const string Major4 = "4"; - private const string FrameworkAssembly = "System.Data"; - private const string CoreAssembly = "System.Data.Common"; - private const string DbCommandTypeName = "System.Data.Common.DbCommand"; - private const string DbDataReaderTypeName = "System.Data.Common.DbDataReader"; - private const string CommandBehaviorTypeName = "System.Data.CommandBehavior"; - - private static readonly Vendors.Serilog.ILogger Log = DatadogLogging.GetLogger(typeof(AdoNetIntegration)); - - /// - /// Wrapper method that instruments . - /// - /// The that is references by the "this" pointer in the instrumented method. - /// A value from . - /// The OpCode used in the original method call. - /// The mdToken of the original method call. - /// A pointer to the module version GUID. - /// The value returned by the instrumented method. - [InterceptMethod( - TargetAssemblies = new[] { FrameworkAssembly, CoreAssembly }, - TargetType = DbCommandTypeName, - TargetSignatureTypes = new[] { DbDataReaderTypeName, CommandBehaviorTypeName }, - TargetMinimumVersion = Major4, - TargetMaximumVersion = Major4)] - public static object ExecuteDbDataReader( - object @this, - int behavior, - int opCode, - int mdToken, - long moduleVersionPtr) - { - if (@this == null) - { - throw new ArgumentNullException(nameof(@this)); - } - - Func instrumentedMethod = null; - var commandBehavior = (CommandBehavior)behavior; - - try - { - var instrumentedType = typeof(DbCommand); - instrumentedMethod = - MethodBuilder> - .Start(moduleVersionPtr, mdToken, opCode, nameof(ExecuteDbDataReader)) - .WithConcreteType(instrumentedType) - .WithParameters(commandBehavior) - .WithNamespaceAndNameFilters(DbDataReaderTypeName, CommandBehaviorTypeName) - .Build(); - } - catch (Exception ex) - { - Log.ErrorRetrievingMethod( - exception: ex, - moduleVersionPointer: moduleVersionPtr, - mdToken: mdToken, - opCode: opCode, - instrumentedType: DbCommandTypeName, - methodName: nameof(ExecuteDbDataReader), - instanceType: @this.GetType().AssemblyQualifiedName); - throw; - } - - using (var scope = CreateScope((DbCommand)@this)) - { - try - { - return instrumentedMethod(@this, commandBehavior); - } - catch (Exception ex) - { - scope?.Span.SetException(ex); - throw; - } - } - } - - /// - /// Wrapper method that instruments . - /// - /// The that is references by the "this" pointer in the instrumented method. - /// A value from . - /// A cancellation token source that can be used to cancel the async operation. - /// The OpCode used in the original method call. - /// The mdToken of the original method call. - /// A pointer to the module version GUID. - /// The value returned by the instrumented method. - [InterceptMethod( - TargetAssemblies = new[] { FrameworkAssembly, CoreAssembly }, - TargetType = DbCommandTypeName, - TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1", CommandBehaviorTypeName, ClrNames.CancellationToken }, - TargetMinimumVersion = Major4, - TargetMaximumVersion = Major4)] - public static object ExecuteDbDataReaderAsync( - object @this, - int behavior, - object cancellationTokenSource, - int opCode, - int mdToken, - long moduleVersionPtr) - { - if (@this == null) - { - throw new ArgumentNullException(nameof(@this)); - } - - var tokenSource = cancellationTokenSource as CancellationTokenSource; - var cancellationToken = tokenSource?.Token ?? CancellationToken.None; - var instrumentedType = typeof(DbCommand); - var dataReaderType = typeof(DbDataReader); - var commandBehavior = (CommandBehavior)behavior; - Func instrumentedMethod = null; - - try - { - instrumentedMethod = - MethodBuilder> - .Start(moduleVersionPtr, mdToken, opCode, nameof(ExecuteDbDataReaderAsync)) - .WithConcreteType(instrumentedType) - .WithParameters(commandBehavior, cancellationToken) - .WithNamespaceAndNameFilters(ClrNames.GenericTask, CommandBehaviorTypeName, ClrNames.CancellationToken) - .Build(); - } - catch (Exception ex) - { - Log.ErrorRetrievingMethod( - exception: ex, - moduleVersionPointer: moduleVersionPtr, - mdToken: mdToken, - opCode: opCode, - instrumentedType: DbCommandTypeName, - methodName: nameof(ExecuteDbDataReaderAsync), - instanceType: @this.GetType().AssemblyQualifiedName); - throw; - } - - return AsyncHelper.InvokeGenericTaskDelegate( - owningType: instrumentedType, - taskResultType: dataReaderType, - nameOfIntegrationMethod: nameof(ExecuteDbDataReaderAsyncInternal), - integrationType: typeof(AdoNetIntegration), - parametersToPass: new object[] { @this, behavior, cancellationToken, instrumentedMethod }); - } - - private static async Task ExecuteDbDataReaderAsyncInternal( - DbCommand command, - CommandBehavior behavior, - CancellationToken cancellationToken, - Func instrumentedMethod) - { - using (var scope = CreateScope(command)) - { - try - { - var task = (Task)instrumentedMethod(command, behavior, cancellationToken); - return await task.ConfigureAwait(false); - } - catch (Exception ex) - { - scope?.Span.SetException(ex); - throw; - } - } - } - - private static Scope CreateScope(DbCommand command) - { - Scope scope = null; - - try - { - Tracer tracer = Tracer.Instance; - - if (!tracer.Settings.IsIntegrationEnabled(IntegrationName)) - { - // integration disabled, don't create a scope, skip this trace - return null; - } - - string dbType = GetDbType(command.GetType().Name); - - if (dbType == null) - { - // don't create a scope, skip this trace - return null; - } - - string serviceName = $"{tracer.DefaultServiceName}-{dbType}"; - string operationName = $"{dbType}.query"; - - scope = tracer.StartActive(operationName, serviceName: serviceName); - var span = scope.Span; - span.SetTag(Tags.DbType, dbType); - span.AddTagsFromDbCommand(command); - - // set analytics sample rate if enabled - var analyticsSampleRate = tracer.Settings.GetIntegrationAnalyticsSampleRate(IntegrationName, enabledWithGlobalSetting: false); - span.SetMetric(Tags.Analytics, analyticsSampleRate); - } - catch (Exception ex) - { - Log.Error(ex, "Error creating or populating scope."); - } - - return scope; - } - - private static string GetDbType(string commandTypeName) - { - switch (commandTypeName) - { - case "SqlCommand": - return "sql-server"; - case "NpgsqlCommand": - return "postgres"; - case "InterceptableDbCommand": - case "ProfiledDbCommand": - // don't create spans for these - return null; - default: - const string commandSuffix = "Command"; - - // remove "Command" suffix if present - return commandTypeName.EndsWith(commandSuffix) - ? commandTypeName.Substring(0, commandTypeName.Length - commandSuffix.Length).ToLowerInvariant() - : commandTypeName.ToLowerInvariant(); - } - } - } -} diff --git a/src/Datadog.Trace.ClrProfiler.Managed/ScopeFactory.cs b/src/Datadog.Trace.ClrProfiler.Managed/ScopeFactory.cs index 6a85e69c6b7a..8efc6d7c363f 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/ScopeFactory.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/ScopeFactory.cs @@ -1,4 +1,6 @@ using System; +using System.Data.Common; +using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Logging; namespace Datadog.Trace.ClrProfiler @@ -62,5 +64,81 @@ public static Scope CreateOutboundHttpScope(Tracer tracer, string httpMethod, Ur // or we couldn't populate it completely (some tags is better than no tags) return scope; } + + public static Scope CreateDbCommandScope(Tracer tracer, DbCommand command, string integrationName) + { + if (!tracer.Settings.IsIntegrationEnabled(integrationName)) + { + // integration disabled, don't create a scope, skip this trace + return null; + } + + Scope scope = null; + + try + { + string dbType = GetDbType(command.GetType().Name); + + if (dbType == null) + { + // don't create a scope, skip this trace + return null; + } + + Span parent = tracer.ActiveScope?.Span; + + if (parent != null && + parent.Type == SpanTypes.Sql && + parent.GetTag(Tags.DbType) == dbType && + parent.ResourceName == command.CommandText) + { + // we are already instrumenting this, + // don't instrument nested methods that belong to the same stacktrace + // e.g. ExecuteReader() -> ExecuteReader(commandBehavior) + return null; + } + + string serviceName = $"{tracer.DefaultServiceName}-{dbType}"; + string operationName = $"{dbType}.query"; + + scope = tracer.StartActive(operationName, serviceName: serviceName); + var span = scope.Span; + span.SetTag(Tags.DbType, dbType); + span.SetTag(Tags.InstrumentationName, integrationName); + span.AddTagsFromDbCommand(command); + + // set analytics sample rate if enabled + var analyticsSampleRate = tracer.Settings.GetIntegrationAnalyticsSampleRate(integrationName, enabledWithGlobalSetting: false); + span.SetMetric(Tags.Analytics, analyticsSampleRate); + } + catch (Exception ex) + { + Log.Error(ex, "Error creating or populating scope."); + } + + return scope; + } + + public static string GetDbType(string commandTypeName) + { + switch (commandTypeName) + { + case "SqlCommand": + return "sql-server"; + case "NpgsqlCommand": + return "postgres"; + case "InterceptableDbCommand": + case "ProfiledDbCommand": + // don't create spans for these + return null; + default: + const string commandSuffix = "Command"; + + // remove "Command" suffix if present + return commandTypeName.EndsWith(commandSuffix) + ? commandTypeName.Substring(0, commandTypeName.Length - commandSuffix.Length).ToLowerInvariant() + : commandTypeName.ToLowerInvariant(); + } + } } } diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs new file mode 100644 index 000000000000..2eeebdd9de8f --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/MySqlCommandTests.cs @@ -0,0 +1,43 @@ +using Datadog.Trace.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet +{ + public class MySqlCommandTests : TestHelper + { + public MySqlCommandTests(ITestOutputHelper output) + : base("MySql", output) + { + } + + [Fact(Skip = "Need to figure out running MySQL in docker containers.")] + [Trait("Category", "EndToEnd")] + public void SubmitsTraces() + { + const int expectedSpanCount = 14; + const string dbType = "mysql"; + const string expectedOperationName = dbType + ".query"; + const string expectedServiceName = "Samples.MySql-" + dbType; + + int agentPort = TcpPortProvider.GetOpenPort(); + + using (var agent = new MockTracerAgent(agentPort)) + using (ProcessResult processResult = RunSampleAndWaitForExit(agent.Port)) + { + Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); + + var spans = agent.WaitForSpans(expectedSpanCount, operationName: expectedOperationName); + Assert.Equal(expectedSpanCount, spans.Count); + + foreach (var span in spans) + { + Assert.Equal(expectedOperationName, span.Name); + Assert.Equal(expectedServiceName, span.Service); + Assert.Equal(SpanTypes.Sql, span.Type); + Assert.Equal(dbType, span.Tags[Tags.DbType]); + } + } + } + } +} diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs new file mode 100644 index 000000000000..66782f2d16b9 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/NpgsqlCommandTests.cs @@ -0,0 +1,43 @@ +using Datadog.Trace.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet +{ + public class NpgsqlCommandTests : TestHelper + { + public NpgsqlCommandTests(ITestOutputHelper output) + : base("Npgsql", output) + { + } + + [Fact] + [Trait("Category", "EndToEnd")] + public void SubmitsTraces() + { + const int expectedSpanCount = 14; + const string dbType = "postgres"; + const string expectedOperationName = dbType + ".query"; + const string expectedServiceName = "Samples.Npgsql-" + dbType; + + int agentPort = TcpPortProvider.GetOpenPort(); + + using (var agent = new MockTracerAgent(agentPort)) + using (ProcessResult processResult = RunSampleAndWaitForExit(agent.Port)) + { + Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); + + var spans = agent.WaitForSpans(expectedSpanCount, operationName: expectedOperationName); + Assert.Equal(expectedSpanCount, spans.Count); + + foreach (var span in spans) + { + Assert.Equal(expectedOperationName, span.Name); + Assert.Equal(expectedServiceName, span.Service); + Assert.Equal(SpanTypes.Sql, span.Type); + Assert.Equal(dbType, span.Tags[Tags.DbType]); + } + } + } + } +} diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/SqlServerTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/SqlCommandTests.cs similarity index 51% rename from test/Datadog.Trace.ClrProfiler.IntegrationTests/SqlServerTests.cs rename to test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/SqlCommandTests.cs index 39f3b41f8eb5..9368cf5e550c 100644 --- a/test/Datadog.Trace.ClrProfiler.IntegrationTests/SqlServerTests.cs +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/AdoNet/SqlCommandTests.cs @@ -2,24 +2,26 @@ using Xunit; using Xunit.Abstractions; -// EFCore targets netstandard2.0, so it requires net461+ or netcoreapp2.0+ -#if !NET452 - -namespace Datadog.Trace.ClrProfiler.IntegrationTests +namespace Datadog.Trace.ClrProfiler.IntegrationTests.AdoNet { - public class SqlServerTests : TestHelper + public class SqlCommandTests : TestHelper { - public SqlServerTests(ITestOutputHelper output) + public SqlCommandTests(ITestOutputHelper output) : base("SqlServer", output) { } [Theory] - [MemberData(nameof(PackageVersions.SqlServer), MemberType = typeof(PackageVersions))] + [MemberData(nameof(PackageVersions.SqlClient), MemberType = typeof(PackageVersions))] [Trait("Category", "EndToEnd")] [Trait("RunOnWindows", "True")] public void SubmitsTraces(string packageVersion) { + const int expectedSpanCount = 28; + const string dbType = "sql-server"; + const string expectedOperationName = dbType + ".query"; + const string expectedServiceName = "Samples.SqlServer-" + dbType; + int agentPort = TcpPortProvider.GetOpenPort(); using (var agent = new MockTracerAgent(agentPort)) @@ -27,17 +29,17 @@ public void SubmitsTraces(string packageVersion) { Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); - var spans = agent.WaitForSpans(4); - Assert.True(spans.Count > 0, "expected at least one span"); + var spans = agent.WaitForSpans(expectedSpanCount, operationName: expectedOperationName); + Assert.Equal(expectedSpanCount, spans.Count); + foreach (var span in spans) { - Assert.Equal("sql-server.query", span.Name); - Assert.Equal($"Samples.SqlServer-sql-server", span.Service); + Assert.Equal(expectedOperationName, span.Name); + Assert.Equal(expectedServiceName, span.Service); Assert.Equal(SpanTypes.Sql, span.Type); + Assert.Equal(dbType, span.Tags[Tags.DbType]); } } } } } - -#endif diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/NpgSqlTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/NpgSqlTests.cs deleted file mode 100644 index d4a98e810791..000000000000 --- a/test/Datadog.Trace.ClrProfiler.IntegrationTests/NpgSqlTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Datadog.Trace.TestHelpers; -using Xunit; -using Xunit.Abstractions; - -namespace Datadog.Trace.ClrProfiler.IntegrationTests -{ - public class NpgsqlTests : TestHelper - { - public NpgsqlTests(ITestOutputHelper output) - : base("Npgsql", output) - { - } - - [Theory] - [MemberData(nameof(PackageVersions.Npgsql), MemberType = typeof(PackageVersions))] - [Trait("Category", "EndToEnd")] - public void SubmitsTraces(string packageVersion) - { - int agentPort = TcpPortProvider.GetOpenPort(); - - using (var agent = new MockTracerAgent(agentPort)) - using (ProcessResult processResult = RunSampleAndWaitForExit(agent.Port, packageVersion: packageVersion)) - { - Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); - - var spans = agent.WaitForSpans(3); - Assert.True(spans.Count > 0, "expected at least one span"); - foreach (var span in spans) - { - Assert.Equal("postgres.query", span.Name); - Assert.Equal("Samples.Npgsql-postgres", span.Service); - Assert.Equal(SpanTypes.Sql, span.Type); - } - } - } - } -} diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/PackageVersions.g.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/PackageVersions.g.cs index 2b847e071976..01a2e7d6c5d9 100644 --- a/test/Datadog.Trace.ClrProfiler.IntegrationTests/PackageVersions.g.cs +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/PackageVersions.g.cs @@ -82,6 +82,7 @@ public class PackageVersions new object[] { "6.8.0" }, new object[] { "6.8.1" }, new object[] { "6.8.2" }, + new object[] { "6.8.3" }, #endif }; @@ -125,33 +126,29 @@ public class PackageVersions new object[] { "4.0.9" }, new object[] { "4.0.10" }, new object[] { "4.1.0" }, + new object[] { "4.1.1" }, #endif }; - public static IEnumerable SqlServer => + public static IEnumerable SqlClient => new List { #if DEFAULT_SAMPLES new object[] { string.Empty }, #else - new object[] { "2.0.0" }, - new object[] { "2.0.1" }, - new object[] { "2.0.2" }, - new object[] { "2.0.3" }, - new object[] { "2.1.0" }, - new object[] { "2.1.1" }, - new object[] { "2.1.2" }, - new object[] { "2.1.3" }, - new object[] { "2.1.4" }, - new object[] { "2.1.8" }, - new object[] { "2.1.11" }, - new object[] { "2.2.0" }, - new object[] { "2.2.1" }, - new object[] { "2.2.2" }, - new object[] { "2.2.3" }, - new object[] { "2.2.4" }, - new object[] { "2.2.6" }, + new object[] { "4.1.0" }, + new object[] { "4.3.0" }, + new object[] { "4.3.1" }, + new object[] { "4.4.0" }, + new object[] { "4.4.1" }, + new object[] { "4.4.2" }, + new object[] { "4.4.3" }, + new object[] { "4.5.0" }, + new object[] { "4.5.1" }, + new object[] { "4.6.0" }, + new object[] { "4.6.1" }, + new object[] { "4.7.0" }, #endif }; diff --git a/test/Datadog.Trace.TestHelpers/MockTracerAgent.cs b/test/Datadog.Trace.TestHelpers/MockTracerAgent.cs index cc2b0128cb3e..3018ec249be9 100644 --- a/test/Datadog.Trace.TestHelpers/MockTracerAgent.cs +++ b/test/Datadog.Trace.TestHelpers/MockTracerAgent.cs @@ -85,13 +85,11 @@ public IImmutableList WaitForSpans( int count, int timeoutInMilliseconds = 20000, string operationName = null, - DateTime? minDateTime = null, + DateTimeOffset? minDateTime = null, bool returnAllOperations = false) { var deadline = DateTime.Now.AddMilliseconds(timeoutInMilliseconds); - - var minimumOffset = - new DateTimeOffset(minDateTime ?? DateTime.MinValue).ToUnixTimeNanoseconds(); + var minimumOffset = (minDateTime ?? DateTimeOffset.MinValue).ToUnixTimeNanoseconds(); IImmutableList relevantSpans = null;