Add support for SqlClient metrics (#3875)

This commit is contained in:
Piotr Kiełkowicz 2024-12-10 06:44:25 +01:00 committed by GitHub
parent 6c299d7a18
commit 3fc3553458
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 269 additions and 50 deletions

View File

@ -12,6 +12,7 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h
- Support for .NET9.
- Support for [RabbitMQ.Client](https://www.nuget.org/packages/RabbitMQ.Client/)
traces instrumentation for versions `7.0.0`+.
- Support for SqlClient metrics.
### Changed

View File

@ -182,17 +182,24 @@ Metrics are stable, but particular instrumentation are in Experimental status
due to lack of stable semantic convention.
| ID | Instrumented library | Documentation | Supported versions | Instrumentation type | Status |
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------|
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `ASPNET` | ASP.NET Framework \[1\] **Not supported on .NET** | [ASP.NET metrics](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/Instrumentation.AspNet-1.9.0-beta.1/src/OpenTelemetry.Instrumentation.AspNet/README.md#list-of-metrics-produced) | * | source & bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `ASPNETCORE` | ASP.NET Core **Not supported on .NET Framework** | [ASP.NET Core metrics](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/Instrumentation.AspNetCore-1.10.0/src/OpenTelemetry.Instrumentation.AspNetCore/README.md#list-of-metrics-produced) | * | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `HTTPCLIENT` | [System.Net.Http.HttpClient](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient) and [System.Net.HttpWebRequest](https://docs.microsoft.com/dotnet/api/system.net.httpwebrequest) | [HttpClient metrics](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/Instrumentation.Http-1.10.0/src/OpenTelemetry.Instrumentation.Http/README.md#list-of-metrics-produced) | * | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `NETRUNTIME` | [OpenTelemetry.Instrumentation.Runtime](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Runtime) | [Runtime metrics](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/Instrumentation.Runtime-1.10.0/src/OpenTelemetry.Instrumentation.Runtime/README.md#metrics) | * | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `PROCESS` | [OpenTelemetry.Instrumentation.Process](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Process) | [Process metrics](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/Instrumentation.Process-1.10.0-beta.1/src/OpenTelemetry.Instrumentation.Process/README.md#metrics) | * | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `NSERVICEBUS` | [NServiceBus](https://www.nuget.org/packages/NServiceBus) | [NServiceBus metrics](https://docs.particular.net/samples/open-telemetry/prometheus-grafana/#reporting-metric-values) | ≥8.0.0 & < 10.0.0 | source & bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `PROCESS` | [OpenTelemetry.Instrumentation.Process](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Process) | [Process metrics](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/Instrumentation.Process-1.10.0-beta.1/src/OpenTelemetry.Instrumentation.Process/README.md#metrics) | * | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `SQLCLIENT` | [Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient), [System.Data.SqlClient](https://www.nuget.org/packages/System.Data.SqlClient) \[2\] and `System.Data` (shipped with .NET Framework) | [SqlClient metrics](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/releases/tag/Instrumentation.SqlClient-1.10.0-beta.1) | * \[3\] | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
\[1\]: The ASP.NET metrics are generated only if the `AspNet` trace instrumentation
is also enabled.
\[2\]: `System.Data.SqlClient` is [deprecated](https://www.nuget.org/packages/System.Data.SqlClient/4.9.0#readme-body-tab).
\[3\]: `Microsoft.Data.SqlClient` v3.* is not supported on .NET Framework,
due to [issue](https://github.com/open-telemetry/opentelemetry-dotnet/issues/4243).
`System.Data.SqlClient` is supported from version 4.8.5.
### Logs instrumentations
**Status**: [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md).

View File

@ -43,7 +43,7 @@ internal static class DelayedInitialization
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddSqlClient(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
{
new SqlClientInitializer(lazyInstrumentationLoader, pluginManager, tracerSettings);
new SqlClientTracerInitializer(lazyInstrumentationLoader, pluginManager, tracerSettings);
}
#if NET
@ -89,5 +89,11 @@ internal static class DelayedInitialization
{
new HttpClientMetricsInitializer(lazyInstrumentationLoader);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddSqlClient(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
{
new SqlClientMetricsInitializer(lazyInstrumentationLoader, pluginManager);
}
}
}

View File

@ -44,6 +44,7 @@ internal static class EnvironmentConfigurationMetricHelper
.AddMeter("Microsoft.AspNetCore.Diagnostics")
.AddMeter("Microsoft.AspNetCore.RateLimiting"),
#endif
MetricInstrumentation.SqlClient => Wrappers.AddSqlClientInstrumentation(builder, lazyInstrumentationLoader, pluginManager),
_ => null,
};
}
@ -115,6 +116,13 @@ internal static class EnvironmentConfigurationMetricHelper
return builder.AddProcessInstrumentation();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static MeterProviderBuilder AddSqlClientInstrumentation(MeterProviderBuilder builder, LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
{
DelayedInitialization.Metrics.AddSqlClient(lazyInstrumentationLoader, pluginManager);
return builder.AddMeter("OpenTelemetry.Instrumentation.SqlClient");
}
// Exporters
[MethodImpl(MethodImplOptions.NoInlining)]

View File

@ -39,6 +39,11 @@ internal enum MetricInstrumentation
/// <summary>
/// ASP.NET Core instrumentation.
/// </summary>
AspNetCore = 6
AspNetCore = 6,
#endif
/// <summary>
/// SqlClient instrumentation.
/// </summary>
SqlClient = 7
}

View File

@ -296,6 +296,9 @@ internal static class Instrumentation
break;
case MetricInstrumentation.NServiceBus:
break;
case MetricInstrumentation.SqlClient:
DelayedInitialization.Metrics.AddSqlClient(lazyInstrumentationLoader, pluginManager);
break;
default:
Logger.Warning($"Configured metric instrumentation type is not supported: {instrumentation}");
if (FailFastSettings.Value.FailFast)

View File

@ -1,23 +1,12 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Reflection;
using OpenTelemetry.AutoInstrumentation.Configurations;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
internal class SqlClientInitializer
internal abstract class SqlClientInitializer
{
private readonly PluginManager _pluginManager;
private readonly TracerSettings _tracerSettings;
private int _initialized;
public SqlClientInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
protected SqlClientInitializer(LazyInstrumentationLoader lazyInstrumentationLoader)
{
_pluginManager = pluginManager;
_tracerSettings = tracerSettings;
lazyInstrumentationLoader.Add(new GenericInitializer("System.Data.SqlClient", InitializeOnFirstCall));
lazyInstrumentationLoader.Add(new GenericInitializer("Microsoft.Data.SqlClient", InitializeOnFirstCall));
@ -26,30 +15,5 @@ internal class SqlClientInitializer
#endif
}
private void InitializeOnFirstCall(ILifespanManager lifespanManager)
{
if (Interlocked.Exchange(ref _initialized, value: 1) != default)
{
// InitializeOnFirstCall() was already called before
return;
}
var instrumentationType = Type.GetType("OpenTelemetry.Instrumentation.SqlClient.SqlClientInstrumentation, OpenTelemetry.Instrumentation.SqlClient")!;
var options = new OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions
{
SetDbStatementForText = _tracerSettings.InstrumentationOptions.SqlClientSetDbStatementForText
};
_pluginManager.ConfigureTracesOptions(options);
var propertyInfo = instrumentationType.GetProperty("TracingOptions", BindingFlags.Static | BindingFlags.Public);
propertyInfo?.SetValue(null, options);
var instrumentation = instrumentationType.InvokeMember("AddTracingHandle", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, null, []);
if (instrumentation != null)
{
lifespanManager.Track(instrumentation);
}
}
protected abstract void InitializeOnFirstCall(ILifespanManager lifespanManager);
}

View File

@ -0,0 +1,37 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Reflection;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
internal sealed class SqlClientMetricsInitializer : SqlClientInitializer
{
private readonly PluginManager _pluginManager;
private int _initialized;
public SqlClientMetricsInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
: base(lazyInstrumentationLoader)
{
_pluginManager = pluginManager;
}
protected override void InitializeOnFirstCall(ILifespanManager lifespanManager)
{
if (Interlocked.Exchange(ref _initialized, value: 1) != default)
{
// InitializeOnFirstCall() was already called before
return;
}
var instrumentationType = Type.GetType("OpenTelemetry.Instrumentation.SqlClient.SqlClientInstrumentation, OpenTelemetry.Instrumentation.SqlClient")!;
var instrumentation = instrumentationType.InvokeMember("AddMetricHandle", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, null, []);
if (instrumentation != null)
{
lifespanManager.Track(instrumentation);
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Reflection;
using OpenTelemetry.AutoInstrumentation.Configurations;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
internal sealed class SqlClientTracerInitializer : SqlClientInitializer
{
private readonly PluginManager _pluginManager;
private readonly TracerSettings _tracerSettings;
private int _initialized;
public SqlClientTracerInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
: base(lazyInstrumentationLoader)
{
_pluginManager = pluginManager;
_tracerSettings = tracerSettings;
}
protected override void InitializeOnFirstCall(ILifespanManager lifespanManager)
{
if (Interlocked.Exchange(ref _initialized, value: 1) != default)
{
// InitializeOnFirstCall() was already called before
return;
}
var instrumentationType = Type.GetType("OpenTelemetry.Instrumentation.SqlClient.SqlClientInstrumentation, OpenTelemetry.Instrumentation.SqlClient")!;
var options = new OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions
{
SetDbStatementForText = _tracerSettings.InstrumentationOptions.SqlClientSetDbStatementForText
};
_pluginManager.ConfigureTracesOptions(options);
var propertyInfo = instrumentationType.GetProperty("TracingOptions", BindingFlags.Static | BindingFlags.Public);
propertyInfo?.SetValue(null, options);
var instrumentation = instrumentationType.InvokeMember("AddTracingHandle", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, null, []);
if (instrumentation != null)
{
lifespanManager.Track(instrumentation);
}
}
}

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using IntegrationTests.Helpers;
using OpenTelemetry.AutoInstrumentation.Configurations;
using Xunit.Abstractions;
namespace IntegrationTests;
@ -38,4 +39,38 @@ public class SqlClientMicrosoftTests : TestHelper
collector.AssertExpectations();
}
[SkippableTheory]
[Trait("Category", "EndToEnd")]
[Trait("Containers", "Linux")]
[MemberData(nameof(LibraryVersion.SqlClientMicrosoft), MemberType = typeof(LibraryVersion))]
public void SubmitMetrics(string packageVersion)
{
// Skip the test if fixture does not support current platform
_sqlServerFixture.SkipIfUnsupportedPlatform();
using var collector = new MockMetricsCollector(Output);
SetExporter(collector);
collector.Expect("OpenTelemetry.Instrumentation.SqlClient");
SetEnvironmentVariable("LONG_RUNNING", "true");
SetEnvironmentVariable("OTEL_METRIC_EXPORT_INTERVAL", "100");
SetEnvironmentVariable(ConfigurationKeys.Traces.TracesEnabled, bool.FalseString); // make sure that traces instrumentation is not needed
using var process = StartTestApplication(new TestSettings
{
Arguments = $"{_sqlServerFixture.Password} {_sqlServerFixture.Port}",
PackageVersion = packageVersion
});
try
{
collector.AssertExpectations();
}
finally
{
process?.Kill();
}
}
}

View File

@ -3,7 +3,7 @@
#if NETFRAMEWORK
using IntegrationTests.Helpers;
using Xunit;
using OpenTelemetry.AutoInstrumentation.Configurations;
using Xunit.Abstractions;
namespace IntegrationTests;
@ -27,5 +27,30 @@ public class SqlClientSystemDataTests : TestHelper
collector.AssertExpectations();
}
[Fact]
[Trait("Category", "EndToEnd")]
public void SubmitMetrics()
{
using var collector = new MockMetricsCollector(Output);
SetExporter(collector);
collector.Expect("OpenTelemetry.Instrumentation.SqlClient");
SetEnvironmentVariable("LONG_RUNNING", "true");
SetEnvironmentVariable("OTEL_METRIC_EXPORT_INTERVAL", "100");
SetEnvironmentVariable(ConfigurationKeys.Traces.TracesEnabled, bool.FalseString); // make sure that traces instrumentation is not needed
using var process = StartTestApplication();
try
{
collector.AssertExpectations();
}
finally
{
process?.Kill();
}
}
}
#endif

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using IntegrationTests.Helpers;
using OpenTelemetry.AutoInstrumentation.Configurations;
using Xunit.Abstractions;
namespace IntegrationTests;
@ -62,4 +63,38 @@ public class SqlClientSystemTests : TestHelper
collector.AssertExpectations();
}
[SkippableTheory]
[Trait("Category", "EndToEnd")]
[Trait("Containers", "Linux")]
[MemberData(nameof(LibraryVersion.SqlClientSystem), MemberType = typeof(LibraryVersion))]
public void SubmitMetrics(string packageVersion)
{
// Skip the test if fixture does not support current platform
_sqlServerFixture.SkipIfUnsupportedPlatform();
using var collector = new MockMetricsCollector(Output);
SetExporter(collector);
collector.Expect("OpenTelemetry.Instrumentation.SqlClient");
SetEnvironmentVariable("LONG_RUNNING", "true");
SetEnvironmentVariable("OTEL_METRIC_EXPORT_INTERVAL", "100");
SetEnvironmentVariable(ConfigurationKeys.Traces.TracesEnabled, bool.FalseString); // make sure that traces instrumentation is not needed
using var process = StartTestApplication(new TestSettings
{
Arguments = $"{_sqlServerFixture.Password} {_sqlServerFixture.Port}",
PackageVersion = packageVersion
});
try
{
collector.AssertExpectations();
}
finally
{
process?.Kill();
}
}
}

View File

@ -345,6 +345,7 @@ public class SettingsTests : IDisposable
#if NET
[InlineData("ASPNETCORE", MetricInstrumentation.AspNetCore)]
#endif
[InlineData("SQLCLIENT", MetricInstrumentation.SqlClient)]
internal void MeterSettings_Instrumentations_SupportedValues(string meterInstrumentation, MetricInstrumentation expectedMetricInstrumentation)
{
Environment.SetEnvironmentVariable(ConfigurationKeys.Metrics.MetricsInstrumentationEnabled, "false");

View File

@ -1,6 +1,7 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Diagnostics;
using Microsoft.Data.SqlClient;
using TestApplication.Shared;
@ -32,6 +33,19 @@ public class Program
{
await ExecuteAsyncCommands(connection);
}
// The "LONG_RUNNING" environment variable is used by tests that access/receive
// data that takes time to be produced.
var longRunning = Environment.GetEnvironmentVariable("LONG_RUNNING");
if (longRunning == "true")
{
// In this case it is necessary to ensure that the test has a chance to read the
// expected data, only by keeping the application alive for some time that can
// be ensured. Anyway, tests that set "LONG_RUNNING" env var to true are expected
// to kill the process directly.
Console.WriteLine("LONG_RUNNING is true, waiting for process to be killed...");
Process.GetCurrentProcess().WaitForExit();
}
}
private static void ExecuteCommands(SqlConnection connection)

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using System.Data.SqlClient;
using System.Diagnostics;
using TestApplication.Shared;
namespace TestApplication.SqlClient.System;
@ -31,6 +32,19 @@ public class Program
{
await ExecuteAsyncCommands(connection);
}
// The "LONG_RUNNING" environment variable is used by tests that access/receive
// data that takes time to be produced.
var longRunning = Environment.GetEnvironmentVariable("LONG_RUNNING");
if (longRunning == "true")
{
// In this case it is necessary to ensure that the test has a chance to read the
// expected data, only by keeping the application alive for some time that can
// be ensured. Anyway, tests that set "LONG_RUNNING" env var to true are expected
// to kill the process directly.
Console.WriteLine("LONG_RUNNING is true, waiting for process to be killed...");
Process.GetCurrentProcess().WaitForExit();
}
}
private static void ExecuteCommands(SqlConnection connection)

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using System.Data.SqlClient;
using System.Diagnostics;
using TestApplication.Shared;
namespace TestApplication.SqlClient.System;
@ -33,6 +34,19 @@ public class Program
{
await ExecuteAsyncCommands(connection);
}
// The "LONG_RUNNING" environment variable is used by tests that access/receive
// data that takes time to be produced.
var longRunning = Environment.GetEnvironmentVariable("LONG_RUNNING");
if (longRunning == "true")
{
// In this case it is necessary to ensure that the test has a chance to read the
// expected data, only by keeping the application alive for some time that can
// be ensured. Anyway, tests that set "LONG_RUNNING" env var to true are expected
// to kill the process directly.
Console.WriteLine("LONG_RUNNING is true, waiting for process to be killed...");
Process.GetCurrentProcess().WaitForExit();
}
}
private static void ExecuteCommands(SqlConnection connection)