diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/PublicAPI.Unshipped.txt index a8e6e1c363..46034fba4e 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/.publicApi/PublicAPI.Unshipped.txt @@ -2,6 +2,7 @@ OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentati OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentation.AddConnection(StackExchange.Redis.IConnectionMultiplexer! connection) -> System.IDisposable! OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentation.AddConnection(string! name, StackExchange.Redis.IConnectionMultiplexer! connection) -> System.IDisposable! OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentation.Dispose() -> void +OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationExtensions OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions.Enrich.get -> System.Action? OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions.Enrich.set -> void @@ -12,13 +13,18 @@ OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentati OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions.SetVerboseDatabaseStatements.get -> bool OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions.SetVerboseDatabaseStatements.set -> void OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions.StackExchangeRedisInstrumentationOptions() -> void -OpenTelemetry.Trace.TracerProviderBuilderExtensions -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, object! serviceKey) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, StackExchange.Redis.IConnectionMultiplexer! connection) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, StackExchange.Redis.IConnectionMultiplexer! connection, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, object! serviceKey, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, StackExchange.Redis.IConnectionMultiplexer? connection, object? serviceKey, System.Action? configure) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +OpenTelemetry.Metrics.StackExchangeRedisMeterProviderBuilderExtensions +OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions +static OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Metrics.StackExchangeRedisMeterProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.StackExchangeRedisMeterProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, object! serviceKey) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.StackExchangeRedisMeterProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, StackExchange.Redis.IConnectionMultiplexer! connection) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.StackExchangeRedisMeterProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, string? name, StackExchange.Redis.IConnectionMultiplexer? connection, object? serviceKey) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, object! serviceKey) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, StackExchange.Redis.IConnectionMultiplexer! connection) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, StackExchange.Redis.IConnectionMultiplexer! connection, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, object! serviceKey, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, StackExchange.Redis.IConnectionMultiplexer? connection, object? serviceKey, System.Action? configure) -> OpenTelemetry.Trace.TracerProviderBuilder! +static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action! configure) -> OpenTelemetry.Trace.TracerProviderBuilder! diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md index 252b9bf246..5e79af0b6d 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md @@ -5,6 +5,12 @@ * Drop support for .NET 6 as this target is no longer supported and add .NET 8 target. ([#2160](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2160)) +* Add `OpenTelemetry.Instrumentation.StackExchangeRedis` meter. Supported metrics: + * `db.client.operation.duration`, + * `db.client.operation.queue_time`, + * `db.client.operation.server_time`. + ([#1982](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1982)) + ## 1.9.0-beta.1 Released 2024-Jul-23 diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisMetrics.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisMetrics.cs new file mode 100644 index 0000000000..f881e0d56d --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisMetrics.cs @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics.Metrics; +using System.Reflection; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; + +internal class RedisMetrics : IDisposable +{ + internal static readonly Assembly Assembly = typeof(StackExchangeRedisInstrumentation).Assembly; + internal static readonly AssemblyName AssemblyName = Assembly.GetName(); + internal static readonly string InstrumentationName = AssemblyName.Name!; + internal static readonly string InstrumentationVersion = Assembly.GetPackageVersion(); + + private readonly Meter meter; + + public RedisMetrics() + { + this.meter = new Meter(InstrumentationName, InstrumentationVersion); + + this.DurationHistogram = this.meter.CreateHistogram( + "db.client.operation.duration", + unit: "s", + description: "Duration of database client operations."); + } + + public static RedisMetrics Instance { get; } = new RedisMetrics(); + + public Histogram DurationHistogram { get; } + + public bool Enabled => this.DurationHistogram.Enabled; + + public void Dispose() + { + this.meter.Dispose(); + } +} diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryInstrumenter.cs similarity index 61% rename from src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs rename to src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryInstrumenter.cs index a46574f451..39a81dc39a 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryInstrumenter.cs @@ -7,15 +7,18 @@ using System.Reflection.Emit; using OpenTelemetry.Trace; using StackExchange.Redis.Profiling; + #if NET using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -#endif +#endif namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; -internal static class RedisProfilerEntryToActivityConverter +internal static class RedisProfilerEntryInstrumenter { + private const int DefaultPort = 6379; + private static readonly Lazy> MessageDataGetter = new(() => { var profiledCommandType = Type.GetType("StackExchange.Redis.Profiling.ProfiledCommand, StackExchange.Redis", throwOnError: true)!; @@ -68,7 +71,11 @@ static bool GetCommandAndKey( }); }); - public static Activity? ProfilerCommandToActivity(Activity? parentActivity, IProfiledCommand command, StackExchangeRedisInstrumentationOptions options) + public static Activity? ProfilerCommandInstrument( + Activity? parentActivity, + IProfiledCommand command, + RedisMetrics metrics, + StackExchangeRedisInstrumentationOptions options) { var name = command.Command; // Example: SET; if (string.IsNullOrEmpty(name)) @@ -83,30 +90,38 @@ static bool GetCommandAndKey( StackExchangeRedisConnectionInstrumentation.CreationTags, startTime: command.CommandCreated); - if (activity == null) + if (activity is null && metrics.Enabled is false) { return null; } - activity.SetEndTime(command.CommandCreated + command.ElapsedTime); + activity?.SetEndTime(command.CommandCreated + command.ElapsedTime); + var meterTags = metrics.Enabled ? + new TagList(StackExchangeRedisConnectionInstrumentation.CreationTags) : + default(IList>); - if (activity.IsAllDataRequested) - { - // see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md + // see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md + + // Timing example: + // command.CommandCreated; //2019-01-10 22:18:28Z - // Timing example: - // command.CommandCreated; //2019-01-10 22:18:28Z + // command.CreationToEnqueued; // 00:00:32.4571995 + // command.EnqueuedToSending; // 00:00:00.0352838 + // command.SentToResponse; // 00:00:00.0060586 + // command.ResponseToCompletion; // 00:00:00.0002601 - // command.CreationToEnqueued; // 00:00:32.4571995 - // command.EnqueuedToSending; // 00:00:00.0352838 - // command.SentToResponse; // 00:00:00.0060586 - // command.ResponseToCompletion; // 00:00:00.0002601 + // Total: + // command.ElapsedTime; // 00:00:32.4988020 - // Total: - // command.ElapsedTime; // 00:00:32.4988020 + activity?.SetTag(SemanticConventions.AttributeDbRedisFlagsKeyName, command.Flags.ToString()); - activity.SetTag(StackExchangeRedisConnectionInstrumentation.RedisFlagsKeyName, command.Flags.ToString()); + if (command.Command != null) + { + meterTags?.Add(SemanticConventions.AttributeDbOperationName, command.Command); + } + if (activity != null) + { if (options.SetVerboseDatabaseStatements) { var (commandAndKey, script) = MessageDataGetter.Value.Invoke(command); @@ -130,31 +145,59 @@ static bool GetCommandAndKey( // Example: "db.statement": SET; activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command); } + } - if (command.EndPoint != null) + if (command.EndPoint != null) + { + if (command.EndPoint is IPEndPoint ipEndPoint) { - if (command.EndPoint is IPEndPoint ipEndPoint) - { - activity.SetTag(SemanticConventions.AttributeNetPeerIp, ipEndPoint.Address.ToString()); - activity.SetTag(SemanticConventions.AttributeNetPeerPort, ipEndPoint.Port); - } - else if (command.EndPoint is DnsEndPoint dnsEndPoint) + var ip = ipEndPoint.Address.ToString(); + var port = ipEndPoint.Port; + + activity?.SetTag(SemanticConventions.AttributeNetPeerIp, ip); + activity?.SetTag(SemanticConventions.AttributeNetPeerPort, port); + + meterTags?.Add(SemanticConventions.AttributeServerAddress, ip); + meterTags?.Add(SemanticConventions.AttributeNetworkPeerAddress, ip); + if (port != DefaultPort) { - activity.SetTag(SemanticConventions.AttributeNetPeerName, dnsEndPoint.Host); - activity.SetTag(SemanticConventions.AttributeNetPeerPort, dnsEndPoint.Port); + meterTags?.Add(SemanticConventions.AttributeServerPort, port); + meterTags?.Add(SemanticConventions.AttributeNetworkPeerPort, port); } - else + } + else if (command.EndPoint is DnsEndPoint dnsEndPoint) + { + var host = dnsEndPoint.Host; + var port = dnsEndPoint.Port; + + activity?.SetTag(SemanticConventions.AttributeNetPeerName, host); + activity?.SetTag(SemanticConventions.AttributeNetPeerPort, port); + + meterTags?.Add(SemanticConventions.AttributeServerAddress, host); + if (port != DefaultPort) { - activity.SetTag(SemanticConventions.AttributePeerService, command.EndPoint.ToString()); + meterTags?.Add(SemanticConventions.AttributeServerPort, port); } } + else + { + var service = command.EndPoint.ToString(); + + activity?.SetTag(SemanticConventions.AttributePeerService, service); + meterTags?.Add(SemanticConventions.AttributeServerAddress, service); + } + } - activity.SetTag(StackExchangeRedisConnectionInstrumentation.RedisDatabaseIndexKeyName, command.Db); + var db = command.Db; + activity?.SetTag(SemanticConventions.AttributeDbRedisDatabaseIndex, db); + meterTags?.Add(SemanticConventions.AttributeDbNamespace, db); - // TODO: deal with the re-transmission - // command.RetransmissionOf; - // command.RetransmissionReason; + // TODO: deal with the re-transmission + // command.RetransmissionOf; + // command.RetransmissionReason; + if (activity?.IsAllDataRequested ?? false) + { var enqueued = command.CommandCreated.Add(command.CreationToEnqueued); var send = enqueued.Add(command.EnqueuedToSending); var response = send.Add(command.SentToResponse); @@ -169,16 +212,25 @@ static bool GetCommandAndKey( options.Enrich?.Invoke(activity, command); } - activity.Stop(); + if (metrics.Enabled && meterTags is TagList meterTagList) + { + metrics.DurationHistogram.Record(command.ElapsedTime.TotalSeconds, meterTagList); + } + + activity?.Stop(); return activity; } - public static void DrainSession(Activity? parentActivity, IEnumerable sessionCommands, StackExchangeRedisInstrumentationOptions options) + public static void DrainSession( + Activity? parentActivity, + IEnumerable sessionCommands, + RedisMetrics redisMetrics, + StackExchangeRedisInstrumentationOptions options) { foreach (var command in sessionCommands) { - ProfilerCommandToActivity(parentActivity, command, options); + ProfilerCommandInstrument(parentActivity, command, redisMetrics, options); } } @@ -224,4 +276,9 @@ public static void DrainSession(Activity? parentActivity, IEnumerable> tags, string ket, object? value) + { + tags?.Add(new KeyValuePair(ket, value)); + } } diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md index cf9efeae8f..fa3387ef7e 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md @@ -13,7 +13,7 @@ This is an [Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), which instruments [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis/) -and collects traces about outgoing calls to Redis. +and collects traces and metrics about outgoing calls to Redis. > [!NOTE] > This component is based on the OpenTelemetry semantic conventions for @@ -41,10 +41,12 @@ dotnet add package OpenTelemetry.Instrumentation.StackExchangeRedis ## Step 2: Enable StackExchange.Redis Instrumentation at application startup StackExchange.Redis instrumentation must be enabled at application startup. -`AddRedisInstrumentation` method on `TracerProviderBuilder` must be called to -enable Redis instrumentation, passing the `IConnectionMultiplexer` instance used -to make Redis calls. Only those Redis calls made using the same instance of the -`IConnectionMultiplexer` will be instrumented. +`AddRedisInstrumentation` method on `TracerProviderBuilder` and/or +`MeterProviderBuilder` must be called to enable Redis instrumentation, passing +the `IConnectionMultiplexer` instance used to make Redis calls. Only those +Redis calls made using the same instance of the `IConnectionMultiplexer` will +be instrumented. Once tracing and metrics are enabled, any instrumented +connection will export both signals. The following example demonstrates adding StackExchange.Redis instrumentation to a console application. This example also sets up the OpenTelemetry Console @@ -66,6 +68,11 @@ public class Program .AddRedisInstrumentation(connection) .AddConsoleExporter() .Build(); + + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddRedisInstrumentation(connection) + .AddConsoleExporter() + .Build(); } } ``` @@ -93,6 +100,10 @@ using var connection = ConnectionMultiplexer.Connect("localhost:6379"); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddRedisInstrumentation(connection) .Build(); + +using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddRedisInstrumentation(connection) + .Build(); ``` Whatever connection is specified will be collected by OpenTelemetry. @@ -168,6 +179,9 @@ StackExchange.Redis by default does not give detailed database statements like what key or script was used during an operation. The `SetVerboseDatabaseStatements` option can be used to enable gathering this more detailed information. +`SetVerboseDatabaseStatements` is not applied to metrics, only the command is +defined in the statement attribute. + The following example shows how to use `SetVerboseDatabaseStatements`. ```csharp @@ -186,6 +200,8 @@ raw `IProfiledCommand` object. The `Enrich` action is called only when `activity.IsAllDataRequested` is `true`. It contains the activity itself (which can be enriched), and the source profiled command object. +The `Enrich` action is not applied for metrics. + The following code snippet shows how to add additional tags using `Enrich`. ```csharp diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs index dd8dd528e7..06b52731ab 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs @@ -17,13 +17,11 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis; /// internal sealed class StackExchangeRedisConnectionInstrumentation : IDisposable { - internal const string RedisDatabaseIndexKeyName = "db.redis.database_index"; - internal const string RedisFlagsKeyName = "db.redis.flags"; internal static readonly Assembly Assembly = typeof(StackExchangeRedisConnectionInstrumentation).Assembly; internal static readonly string ActivitySourceName = Assembly.GetName().Name!; internal static readonly string ActivityName = ActivitySourceName + ".Execute"; internal static readonly ActivitySource ActivitySource = new(ActivitySourceName, Assembly.GetPackageVersion()); - internal static readonly IEnumerable> CreationTags = + internal static readonly KeyValuePair[] CreationTags = [ new KeyValuePair(SemanticConventions.AttributeDbSystem, "redis") ]; @@ -50,6 +48,7 @@ public StackExchangeRedisConnectionInstrumentation( { Guard.ThrowIfNull(connection); + this.Connection = connection; this.options = options ?? new StackExchangeRedisInstrumentationOptions(); this.drainThread = new Thread(this.DrainEntries) @@ -62,6 +61,8 @@ public StackExchangeRedisConnectionInstrumentation( connection.RegisterProfiler(this.GetProfilerSessionsFactory()); } + internal IConnectionMultiplexer Connection { get; } + /// /// Returns session for the Redis calls recording. /// @@ -108,7 +109,7 @@ public void Dispose() internal void Flush() { - RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling(), this.options); + RedisProfilerEntryInstrumenter.DrainSession(null, this.defaultSession.FinishProfiling(), RedisMetrics.Instance, this.options); foreach (var entry in this.Cache) { @@ -120,7 +121,7 @@ internal void Flush() } var session = entry.Value.Session; - RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling(), this.options); + RedisProfilerEntryInstrumenter.DrainSession(parent, session.FinishProfiling(), RedisMetrics.Instance, this.options); this.Cache.TryRemove((entry.Key.TraceId, entry.Key.SpanId), out _); } } diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentation.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentation.cs index 41e6671ace..e1cf64246e 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentation.cs @@ -45,9 +45,13 @@ public IDisposable AddConnection(string name, IConnectionMultiplexer connection) lock (this.InstrumentedConnections) { - var instrumentation = new StackExchangeRedisConnectionInstrumentation(connection, name, options); + var instrumentation = this.InstrumentedConnections.FirstOrDefault(i => i.Connection == connection); + if (instrumentation is null) + { + instrumentation = new StackExchangeRedisConnectionInstrumentation(connection, name, options); - this.InstrumentedConnections.Add(instrumentation); + this.InstrumentedConnections.Add(instrumentation); + } return new StackExchangeRedisConnectionInstrumentationRegistration(() => { @@ -76,15 +80,10 @@ public void Dispose() } } - private sealed class StackExchangeRedisConnectionInstrumentationRegistration : IDisposable + private sealed class StackExchangeRedisConnectionInstrumentationRegistration( + Action disposalAction) : IDisposable { - private readonly Action disposalAction; - - public StackExchangeRedisConnectionInstrumentationRegistration( - Action disposalAction) - { - this.disposalAction = disposalAction; - } + private readonly Action disposalAction = disposalAction; public void Dispose() { diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentationExtensions.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentationExtensions.cs new file mode 100644 index 0000000000..36c64cb802 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentationExtensions.cs @@ -0,0 +1,123 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using OpenTelemetry.Internal; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using StackExchange.Redis; + +namespace OpenTelemetry.Instrumentation.StackExchangeRedis; + +/// +/// Extension methods to simplify registering of Redis instrumentation. +/// +public static class StackExchangeRedisInstrumentationExtensions +{ + /// + /// Registers a callback for configuring Redis instrumentation. + /// + /// being configured. + /// Callback to configure instrumentation. + /// The instance of to chain the calls. + public static TracerProviderBuilder ConfigureRedisInstrumentation( + this TracerProviderBuilder builder, + Action configure) + { + Guard.ThrowIfNull(configure); + + return ConfigureRedisInstrumentation(builder, (sp, instrumentation) => configure(instrumentation)); + } + + /// + /// Registers a callback for configuring Redis instrumentation. + /// + /// being configured. + /// Callback to configure instrumentation. + /// The instance of to chain the calls. + public static TracerProviderBuilder ConfigureRedisInstrumentation( + this TracerProviderBuilder builder, + Action configure) + { + Guard.ThrowIfNull(configure); + + if (builder is not IDeferredTracerProviderBuilder deferredTracerProviderBuilder) + { + throw new NotSupportedException("ConfigureRedisInstrumentation is not supported on the supplied builder type."); + } + + builder.AddRedisInstrumentationSharedServices(); + + deferredTracerProviderBuilder.Configure( + (sp, builder) => configure(sp, sp.GetRequiredService())); + + return builder; + } + + internal static TracerProviderBuilder AddRedisInstrumentationSharedServices( + this TracerProviderBuilder builder) + { + Guard.ThrowIfNull(builder); + + return builder.ConfigureServices(AddRedisInstrumentationSharedServices); + } + + internal static MeterProviderBuilder AddRedisInstrumentationSharedServices( + this MeterProviderBuilder builder) + { + Guard.ThrowIfNull(builder); + + return builder.ConfigureServices(AddRedisInstrumentationSharedServices); + } + + internal static void AddRedisInstrumentationSharedServices(IServiceCollection services) + { + services.TryAddSingleton( + sp => new StackExchangeRedisInstrumentation( + sp.GetRequiredService>())); + } + + internal static TracerProviderBuilder AddInstrumentation( + this TracerProviderBuilder builder, + string name, + IConnectionMultiplexer? connection, + object? serviceKey) + { + return builder + .AddInstrumentation(InstrumentationFactory(name, connection, serviceKey)); + } + + internal static MeterProviderBuilder AddInstrumentation( + this MeterProviderBuilder builder, + string name, + IConnectionMultiplexer? connection, + object? serviceKey) + { + return builder + .AddInstrumentation(InstrumentationFactory(name, connection, serviceKey)); + } + + internal static Func InstrumentationFactory( + string name, + IConnectionMultiplexer? connection, + object? serviceKey) + { + return sp => + { + var instrumentation = sp.GetRequiredService(); + + connection ??= serviceKey == null + ? sp.GetService() + : sp.GetKeyedService(serviceKey); + + if (connection != null) + { + instrumentation.AddConnection(name, connection); + } + + return instrumentation; + }; + } +} diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisMeterProviderBuilderExtensions.cs new file mode 100644 index 0000000000..ba6f2fe086 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisMeterProviderBuilderExtensions.cs @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Microsoft.Extensions.Options; +using OpenTelemetry.Instrumentation.StackExchangeRedis; +using OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; +using OpenTelemetry.Internal; +using StackExchange.Redis; + +namespace OpenTelemetry.Metrics; + +/// +/// Extension methods to simplify registering of StackExchangeRedis request instrumentation. +/// +public static class StackExchangeRedisMeterProviderBuilderExtensions +{ + /// + /// Enables automatic data collection of outgoing requests to Redis. + /// + /// + /// Note: A will be resolved using the + /// application . + /// + /// being configured. + /// The instance of to chain the calls. + public static MeterProviderBuilder AddRedisInstrumentation( + this MeterProviderBuilder builder) + => AddRedisInstrumentation(builder, name: null, connection: null, serviceKey: null); + + /// + /// Enables automatic data collection of outgoing requests to Redis. + /// + /// being configured. + /// to instrument. + /// The instance of to chain the calls. + public static MeterProviderBuilder AddRedisInstrumentation( + this MeterProviderBuilder builder, + IConnectionMultiplexer connection) + { + Guard.ThrowIfNull(connection); + + return AddRedisInstrumentation(builder, name: null, connection, serviceKey: null); + } + + /// + /// Enables automatic data collection of outgoing requests to Redis. + /// + /// being configured. + /// Optional service key used to retrieve the to instrument from the . + /// The instance of to chain the calls. + public static MeterProviderBuilder AddRedisInstrumentation( + this MeterProviderBuilder builder, + object serviceKey) + { + Guard.ThrowIfNull(serviceKey); + + return AddRedisInstrumentation(builder, name: null, connection: null, serviceKey); + } + + /// + /// Enables automatic data collection of outgoing requests to Redis. + /// + /// + /// Note: If an is not supplied + /// using the parameter it will be + /// resolved using the application . + /// + /// being configured. + /// Optional name which is used when retrieving options. + /// Optional to instrument. + /// Optional service key used to retrieve the to instrument from the . + /// The instance of to chain the calls. + public static MeterProviderBuilder AddRedisInstrumentation( + this MeterProviderBuilder builder, + string? name, + IConnectionMultiplexer? connection, + object? serviceKey) + { + Guard.ThrowIfNull(builder); + + name ??= Options.DefaultName; + + builder.AddRedisInstrumentationSharedServices(); + + return builder + .AddMeter(RedisMetrics.InstrumentationName) + .AddInstrumentation(name, connection, serviceKey); + } +} diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisTracerProviderBuilderExtensions.cs similarity index 70% rename from src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs rename to src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisTracerProviderBuilderExtensions.cs index d9a450a2a6..12ee54b539 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisTracerProviderBuilderExtensions.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using OpenTelemetry.Instrumentation.StackExchangeRedis; using OpenTelemetry.Internal; @@ -13,7 +12,7 @@ namespace OpenTelemetry.Trace; /// /// Extension methods to simplify registering of dependency instrumentation. /// -public static class TracerProviderBuilderExtensions +public static class StackExchangeRedisTracerProviderBuilderExtensions { /// /// Enables automatic data collection of outgoing requests to Redis. @@ -152,73 +151,6 @@ public static TracerProviderBuilder AddRedisInstrumentation( return builder .AddSource(StackExchangeRedisConnectionInstrumentation.ActivitySourceName) - .AddInstrumentation(sp => - { - var instrumentation = sp.GetRequiredService(); - - connection ??= serviceKey == null - ? sp.GetService() - : sp.GetKeyedService(serviceKey); - - if (connection != null) - { - instrumentation.AddConnection(name, connection); - } - - return instrumentation; - }); - } - - /// - /// Registers a callback for configuring Redis instrumentation. - /// - /// being configured. - /// Callback to configure instrumentation. - /// The instance of to chain the calls. - public static TracerProviderBuilder ConfigureRedisInstrumentation( - this TracerProviderBuilder builder, - Action configure) - { - Guard.ThrowIfNull(configure); - - return ConfigureRedisInstrumentation(builder, (sp, instrumentation) => configure(instrumentation)); - } - - /// - /// Registers a callback for configuring Redis instrumentation. - /// - /// being configured. - /// Callback to configure instrumentation. - /// The instance of to chain the calls. - public static TracerProviderBuilder ConfigureRedisInstrumentation( - this TracerProviderBuilder builder, - Action configure) - { - Guard.ThrowIfNull(configure); - - if (builder is not IDeferredTracerProviderBuilder deferredTracerProviderBuilder) - { - throw new NotSupportedException("ConfigureRedisInstrumentation is not supported on the supplied builder type."); - } - - builder.AddRedisInstrumentationSharedServices(); - - deferredTracerProviderBuilder.Configure( - (sp, builder) => configure(sp, sp.GetRequiredService())); - - return builder; - } - - private static TracerProviderBuilder AddRedisInstrumentationSharedServices( - this TracerProviderBuilder builder) - { - Guard.ThrowIfNull(builder); - - return builder.ConfigureServices(services => - { - services.TryAddSingleton( - sp => new StackExchangeRedisInstrumentation( - sp.GetRequiredService>())); - }); + .AddInstrumentation(name, connection, serviceKey); } } diff --git a/src/Shared/SemanticConventions.cs b/src/Shared/SemanticConventions.cs index fe87612551..e7eaaf7613 100644 --- a/src/Shared/SemanticConventions.cs +++ b/src/Shared/SemanticConventions.cs @@ -54,6 +54,7 @@ internal static class SemanticConventions public const string AttributeDbInstance = "db.instance"; public const string AttributeDbCassandraKeyspace = "db.cassandra.keyspace"; public const string AttributeDbHBaseNamespace = "db.hbase.namespace"; + public const string AttributeDbRedisFlagsKeyName = "db.redis.flags"; public const string AttributeDbRedisDatabaseIndex = "db.redis.database_index"; public const string AttributeDbMongoDbCollection = "db.mongodb.collection"; @@ -110,6 +111,8 @@ internal static class SemanticConventions // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-metrics.md#http-server public const string AttributeClientAddress = "client.address"; public const string AttributeClientPort = "client.port"; + public const string AttributeNetworkPeerAddress = "network.peer.address"; + public const string AttributeNetworkPeerPort = "network.peer.port"; public const string AttributeNetworkProtocolVersion = "network.protocol.version"; // replaces: "http.flavor" (AttributeHttpFlavor) public const string AttributeNetworkProtocolName = "network.protocol.name"; public const string AttributeServerAddress = "server.address"; // replaces: "net.host.name" (AttributeNetHostName) @@ -144,7 +147,6 @@ internal static class SemanticConventions public const string AttributeDbResponseStatusCode = "db.response.status_code"; public const string AttributeDbOperationBatchSize = "db.operation.batch.size"; public const string AttributeDbQuerySummary = "db.query.summary"; - public const string AttributeDbQueryText = "db.query.text"; #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterConfigurationTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterConfigurationTests.cs new file mode 100644 index 0000000000..9b2eff3352 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterConfigurationTests.cs @@ -0,0 +1,84 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; +using OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using StackExchange.Redis; +using Xunit; + +namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Tests.Implementation; + +[Collection("Redis")] +public class RedisProfilerEntryInstrumenterConfigurationTests +{ + private const int MaxTimeToAllowForFlush = 20000; + + private readonly ConnectionMultiplexer connection; + private readonly RedisMetrics metrics; + private readonly List exportedItems = []; + + public RedisProfilerEntryInstrumenterConfigurationTests() + { + var connectionOptions = new ConfigurationOptions + { + AbortOnConnectFail = false, + }; + connectionOptions.EndPoints.Add("localhost:6379"); + + this.connection = ConnectionMultiplexer.Connect(connectionOptions); + this.metrics = new RedisMetrics(); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public Task RedisProfilerEntryInstrumenter_WhenTracesAndMeters(bool enableMeter) + { + var activity = new Activity("redis-profiler"); + var profiledCommand = new TestProfiledCommand(DateTime.UtcNow); + _ = this.CreateTraceProvider(true); + var meterProvider = this.CreateMeterProvider(enableMeter); + + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); + + Assert.NotNull(result); + Assert.Equal("SET", result.DisplayName); + + meterProvider.ForceFlush(MaxTimeToAllowForFlush); + if (enableMeter) + { + Assert.Single(this.exportedItems); + } + else + { + Assert.Empty(this.exportedItems); + } + + return Task.CompletedTask; + } + + private TracerProvider CreateTraceProvider(bool addInstrumentation) + { + var builder = Sdk.CreateTracerProviderBuilder(); + if (addInstrumentation) + { + builder.AddRedisInstrumentation(this.connection); + } + + return builder.Build()!; + } + + private MeterProvider CreateMeterProvider(bool addInstrumentation) + { + var builder = Sdk.CreateMeterProviderBuilder() + .AddInMemoryExporter(this.exportedItems); + if (addInstrumentation) + { + builder.AddRedisInstrumentation(this.connection); + } + + return builder.Build()!; + } +} diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterTests.cs similarity index 59% rename from test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs rename to test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterTests.cs index bf67721ea8..569bee1f8d 100644 --- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterTests.cs @@ -3,11 +3,11 @@ using System.Diagnostics; using System.Net; -using OpenTelemetry.Instrumentation.StackExchangeRedis.Tests; - #if !NETFRAMEWORK using System.Net.Sockets; #endif +using OpenTelemetry.Instrumentation.StackExchangeRedis.Tests; +using OpenTelemetry.Metrics; using OpenTelemetry.Trace; using StackExchange.Redis; using Xunit; @@ -15,12 +15,13 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; [Collection("Redis")] -public class RedisProfilerEntryToActivityConverterTests : IDisposable +public class RedisProfilerEntryInstrumenterTests : IDisposable { private readonly ConnectionMultiplexer connection; + private readonly RedisMetrics metrics; private readonly TracerProvider tracerProvider; - public RedisProfilerEntryToActivityConverterTests() + public RedisProfilerEntryInstrumenterTests() { var connectionOptions = new ConfigurationOptions { @@ -29,6 +30,7 @@ public RedisProfilerEntryToActivityConverterTests() connectionOptions.EndPoints.Add("localhost:6379"); this.connection = ConnectionMultiplexer.Connect(connectionOptions); + this.metrics = new RedisMetrics(); this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddRedisInstrumentation(this.connection) @@ -43,37 +45,50 @@ public void Dispose() } [Fact] - public void ProfilerCommandToActivity_UsesCommandAsName() + public void ProfilerCommandInstrument_UsesCommandAsName() { var activity = new Activity("redis-profiler"); var profiledCommand = new TestProfiledCommand(DateTime.UtcNow); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); Assert.Equal("SET", result.DisplayName); } [Fact] - public void ProfilerCommandToActivity_UsesTimestampAsStartTime() + public void ProfilerCommandInstrument_UsesDbRedisDatabaseIndex() + { + var activity = new Activity("redis-profiler"); + var profiledCommand = new TestProfiledCommand(DateTime.UtcNow); + + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); + + Assert.NotNull(result); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbRedisDatabaseIndex)); + Assert.Equal(0, result.GetTagValue(SemanticConventions.AttributeDbRedisDatabaseIndex)); + } + + [Fact] + public void ProfilerCommandInstrument_UsesTimestampAsStartTime() { var now = DateTimeOffset.Now; var activity = new Activity("redis-profiler"); var profiledCommand = new TestProfiledCommand(now.DateTime); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); Assert.Equal(now, result.StartTimeUtc); } [Fact] - public void ProfilerCommandToActivity_SetsDbTypeAttributeAsRedis() + public void ProfilerCommandInstrument_SetsDbTypeAttributeAsRedis() { var activity = new Activity("redis-profiler"); var profiledCommand = new TestProfiledCommand(DateTime.UtcNow); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbSystem)); @@ -81,12 +96,12 @@ public void ProfilerCommandToActivity_SetsDbTypeAttributeAsRedis() } [Fact] - public void ProfilerCommandToActivity_UsesCommandAsDbStatementAttribute() + public void ProfilerCommandInstrument_UsesCommandAsDbStatementAttribute() { var activity = new Activity("redis-profiler"); var profiledCommand = new TestProfiledCommand(DateTime.UtcNow); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbStatement)); @@ -94,51 +109,52 @@ public void ProfilerCommandToActivity_UsesCommandAsDbStatementAttribute() } [Fact] - public void ProfilerCommandToActivity_UsesFlagsForFlagsAttribute() + public void ProfilerCommandInstrument_UsesFlagsForFlagsAttribute() { var activity = new Activity("redis-profiler"); var profiledCommand = new TestProfiledCommand(DateTime.UtcNow, CommandFlags.FireAndForget | CommandFlags.NoRedirect); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); - Assert.NotNull(result.GetTagValue(StackExchangeRedisConnectionInstrumentation.RedisFlagsKeyName)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbRedisFlagsKeyName)); #if NET8_0 - Assert.Equal("FireAndForget, NoRedirect", result.GetTagValue(StackExchangeRedisConnectionInstrumentation.RedisFlagsKeyName)); + Assert.Equal("FireAndForget, NoRedirect", result.GetTagValue(SemanticConventions.AttributeDbRedisFlagsKeyName)); #else - Assert.Equal("PreferMaster, FireAndForget, NoRedirect", result.GetTagValue(StackExchangeRedisConnectionInstrumentation.RedisFlagsKeyName)); + Assert.Equal("PreferMaster, FireAndForget, NoRedirect", result.GetTagValue(SemanticConventions.AttributeDbRedisFlagsKeyName)); #endif } [Fact] - public void ProfilerCommandToActivity_UsesIpEndPointAsEndPoint() + public void ProfilerCommandInstrument_UsesIpEndPointAsEndPoint() { long address = 1; int port = 2; + string addressString = $"{address}.0.0.0"; var activity = new Activity("redis-profiler"); IPEndPoint ipLocalEndPoint = new IPEndPoint(address, port); var profiledCommand = new TestProfiledCommand(DateTime.UtcNow, ipLocalEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerIp)); - Assert.Equal($"{address}.0.0.0", result.GetTagValue(SemanticConventions.AttributeNetPeerIp)); + Assert.Equal(addressString, result.GetTagValue(SemanticConventions.AttributeNetPeerIp)); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerPort)); Assert.Equal(port, result.GetTagValue(SemanticConventions.AttributeNetPeerPort)); } [Fact] - public void ProfilerCommandToActivity_UsesDnsEndPointAsEndPoint() + public void ProfilerCommandInstrument_UsesDnsEndPointAsEndPoint() { var dnsEndPoint = new DnsEndPoint("https://opentelemetry.io/", 443); var activity = new Activity("redis-profiler"); var profiledCommand = new TestProfiledCommand(DateTime.UtcNow, dnsEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerName)); @@ -149,13 +165,13 @@ public void ProfilerCommandToActivity_UsesDnsEndPointAsEndPoint() #if !NETFRAMEWORK [Fact] - public void ProfilerCommandToActivity_UsesOtherEndPointAsEndPoint() + public void ProfilerCommandInstrument_UsesOtherEndPointAsEndPoint() { var unixEndPoint = new UnixDomainSocketEndPoint("https://opentelemetry.io/"); var activity = new Activity("redis-profiler"); var profiledCommand = new TestProfiledCommand(DateTime.UtcNow, unixEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand, new StackExchangeRedisInstrumentationOptions()); + var result = RedisProfilerEntryInstrumenter.ProfilerCommandInstrument(activity, profiledCommand, this.metrics, new StackExchangeRedisInstrumentationOptions()); Assert.NotNull(result); Assert.NotNull(result.GetTagValue(SemanticConventions.AttributePeerService)); diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs index 4ede2553fa..e675109f88 100644 --- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs @@ -370,11 +370,15 @@ public void StackExchangeRedis_StackExchangeRedisInstrumentation_Test() { Assert.NotNull(instrumentation); - var registration = instrumentation.AddConnection(connection); + var registration1 = instrumentation.AddConnection(connection); Assert.NotEmpty(instrumentation.InstrumentedConnections); - registration.Dispose(); + var registration2 = instrumentation.AddConnection(connection); + + Assert.Single(instrumentation.InstrumentedConnections); + + registration1.Dispose(); Assert.Empty(instrumentation.InstrumentedConnections); @@ -413,7 +417,7 @@ private static void VerifyActivityData(Activity activity, bool isSet, EndPoint e Assert.Equal(Status.Unset, activity.GetStatus()); Assert.Equal("redis", activity.GetTagValue(SemanticConventions.AttributeDbSystem)); - Assert.Equal(0, activity.GetTagValue(StackExchangeRedisConnectionInstrumentation.RedisDatabaseIndexKeyName)); + Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeDbRedisDatabaseIndex)); if (endPoint is IPEndPoint ipEndPoint) {