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 60254ac153..92f416d561 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +* Add `OpenTelemetry.Instrumentation.StackExchangeRedis` Meter +([#1982](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1982)) +* New Metrics: `redis.client.request.duration`, +`redis.client.request.waiting_response`, `redis.client.request.time_in_queue` + ## 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..d36f6e2b9a --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisMetrics.cs @@ -0,0 +1,57 @@ +// 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 const string MetricRequestDurationName = "redis.client.request.duration"; + internal const string MetricQueueDurationName = "redis.client.queue.duration"; + internal const string MetricNetworkDurationName = "redis.client.network.duration"; + + 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.QueueHistogram = this.meter.CreateHistogram( + MetricQueueDurationName, + unit: "s", + description: "Total time the redis request was waiting in queue before being sent to the server."); + + this.NetworkHistogram = this.meter.CreateHistogram( + MetricNetworkDurationName, + unit: "s", + description: "Duration of redis requests since sent the request to receive the response."); + + this.RequestHistogram = this.meter.CreateHistogram( + MetricRequestDurationName, + unit: "s", + description: "Total client request duration, including processing, queue and server duration."); + } + + public static RedisMetrics Instance { get; } = new RedisMetrics(); + + public Histogram QueueHistogram { get; } + + public Histogram NetworkHistogram { get; } + + public Histogram RequestHistogram { get; } + + public bool Enabled => this.RequestHistogram.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 56% rename from src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs rename to src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryInstrumenter.cs index 5b3a338383..f041d63325 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryInstrumenter.cs @@ -14,7 +14,7 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; -internal static class RedisProfilerEntryToActivityConverter +internal static class RedisProfilerEntryInstrumenter { private static readonly Lazy> MessageDataGetter = new(() => { @@ -73,7 +73,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)) @@ -88,102 +92,148 @@ 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 + var flags = command.Flags.ToString(); + activity?.SetTag(SemanticConventions.AttributeDbRedisFlagsKeyName, flags); + meterTags?.Add(SemanticConventions.AttributeDbRedisFlagsKeyName, flags); - activity.SetTag(StackExchangeRedisConnectionInstrumentation.RedisFlagsKeyName, command.Flags.ToString()); + var operationName = command.Command ?? string.Empty; + activity?.SetTag(SemanticConventions.AttributeDbOperationName, operationName); + meterTags?.Add(SemanticConventions.AttributeDbOperationName, operationName); + if (activity is not null) + { if (options.SetVerboseDatabaseStatements) { var (commandAndKey, script) = MessageDataGetter.Value.Invoke(command); if (!string.IsNullOrEmpty(commandAndKey) && !string.IsNullOrEmpty(script)) { - activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey + " " + script); + activity.SetTag(SemanticConventions.AttributeDbQueryText, commandAndKey + " " + script); } else if (!string.IsNullOrEmpty(commandAndKey)) { - activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey); + activity.SetTag(SemanticConventions.AttributeDbQueryText, commandAndKey); } else if (command.Command != null) { - // Example: "db.statement": SET; - activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command); + // Example: "db.query.text": SET; + activity.SetTag(SemanticConventions.AttributeDbQueryText, command.Command); } } else if (command.Command != null) { - // Example: "db.statement": SET; - activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command); + // Example: "db.query.text": SET; + activity.SetTag(SemanticConventions.AttributeDbQueryText, 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) - { - activity.SetTag(SemanticConventions.AttributeNetPeerName, dnsEndPoint.Host); - activity.SetTag(SemanticConventions.AttributeNetPeerPort, dnsEndPoint.Port); - } - else - { - activity.SetTag(SemanticConventions.AttributePeerService, command.EndPoint.ToString()); - } + var ip = ipEndPoint.Address.ToString(); + var port = ipEndPoint.Port; + + activity?.SetTag(SemanticConventions.AttributeServerAddress, ip); + activity?.SetTag(SemanticConventions.AttributeServerPort, port); + activity?.SetTag(SemanticConventions.AttributeNetworkPeerAddress, ip); + activity?.SetTag(SemanticConventions.AttributeNetworkPeerPort, port); + + meterTags?.Add(SemanticConventions.AttributeServerAddress, ip); + meterTags?.Add(SemanticConventions.AttributeServerPort, port); + meterTags?.Add(SemanticConventions.AttributeNetworkPeerAddress, ip); + meterTags?.Add(SemanticConventions.AttributeNetworkPeerPort, port); } + else if (command.EndPoint is DnsEndPoint dnsEndPoint) + { + var host = dnsEndPoint.Host; + var port = dnsEndPoint.Port; - activity.SetTag(StackExchangeRedisConnectionInstrumentation.RedisDatabaseIndexKeyName, command.Db); + activity?.SetTag(SemanticConventions.AttributeServerAddress, host); + activity?.SetTag(SemanticConventions.AttributeServerPort, port); - // TODO: deal with the re-transmission - // command.RetransmissionOf; - // command.RetransmissionReason; + meterTags?.Add(SemanticConventions.AttributeServerAddress, host); + meterTags?.Add(SemanticConventions.AttributeServerPort, port); + } + else + { + var service = command.EndPoint.ToString(); + + activity?.SetTag(SemanticConventions.AttributeServerAddress, service); + meterTags?.Add(SemanticConventions.AttributeServerAddress, service); + } + } + var db = command.Db; + activity?.SetTag(SemanticConventions.AttributeDbNamespace, db); + meterTags?.Add(SemanticConventions.AttributeDbNamespace, db); + + // 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); + var completion = send.Add(command.ResponseToCompletion); if (options.EnrichActivityWithTimingEvents) { activity.AddEvent(new ActivityEvent("Enqueued", enqueued)); activity.AddEvent(new ActivityEvent("Sent", send)); activity.AddEvent(new ActivityEvent("ResponseReceived", response)); + activity.AddEvent(new ActivityEvent("Completion", completion)); } options.Enrich?.Invoke(activity, command); } - activity.Stop(); + if (metrics.Enabled && meterTags is TagList meterTagList) + { + metrics.QueueHistogram.Record(command.EnqueuedToSending.TotalSeconds, meterTagList); + metrics.NetworkHistogram.Record(command.SentToResponse.TotalSeconds, meterTagList); + metrics.RequestHistogram.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); } } @@ -229,4 +279,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 947c818153..4fdcf03e8e 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md @@ -8,7 +8,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 @@ -36,10 +36,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 @@ -61,6 +63,11 @@ public class Program .AddRedisInstrumentation(connection) .AddConsoleExporter() .Build(); + + using var tracerProvider = Sdk.CreateMeterProviderBuilder() + .AddRedisInstrumentation() + .AddConsoleExporter() + .Build(); } } ``` @@ -88,6 +95,10 @@ using var connection = ConnectionMultiplexer.Connect("localhost:6379"); using var tracerProvider = Sdk.CreateTracerProviderBuilder() .AddRedisInstrumentation(connection) .Build(); + +using var tracerProvider = Sdk.CreateMeterProviderBuilder() + .AddRedisInstrumentation() + .Build(); ``` Whatever connection is specified will be collected by OpenTelemetry. @@ -163,6 +174,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 @@ -181,6 +195,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 08711711b7..735cdeb849 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs @@ -17,16 +17,14 @@ 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 = new[] - { + internal static readonly KeyValuePair[] CreationTags = + [ new KeyValuePair(SemanticConventions.AttributeDbSystem, "redis"), - }; + ]; internal readonly ConcurrentDictionary<(ActivityTraceId TraceId, ActivitySpanId SpanId), (Activity Activity, ProfilingSession Session)> Cache = new(); @@ -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() } ProfilingSession 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 4436a42df4..e1cf64246e 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentation.cs @@ -20,7 +20,7 @@ internal StackExchangeRedisInstrumentation( this.options = options; } - internal List InstrumentedConnections { get; } = new(); + internal List InstrumentedConnections { get; } = []; /// /// Adds an to the instrumentation. @@ -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..1001c30339 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisInstrumentationExtensions.cs @@ -0,0 +1,120 @@ +// 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; + +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 9f1c1ee234..6d86b8fe8c 100644 --- a/src/Shared/SemanticConventions.cs +++ b/src/Shared/SemanticConventions.cs @@ -55,7 +55,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 AttributeDbRedisDatabaseIndex = "db.redis.database_index"; + public const string AttributeDbRedisFlagsKeyName = "db.redis.flags"; public const string AttributeDbMongoDbCollection = "db.mongodb.collection"; public const string AttributeRpcSystem = "rpc.system"; @@ -111,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) @@ -136,5 +138,10 @@ internal static class SemanticConventions public const string AttributeMessagingKafkaMessageKey = "messaging.kafka.message.key"; public const string AttributeMessagingKafkaMessageOffset = "messaging.kafka.message.offset"; + // v1.26.0 Db + public const string AttributeDbNamespace = "db.namespace"; + public const string AttributeDbOperationName = "db.operation.name"; + 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..089a692bcd --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterConfigurationTests.cs @@ -0,0 +1,83 @@ +// 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); + } + + [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.True(this.exportedItems.Count >= 3); + } + 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 51% rename from test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs rename to test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryInstrumenterTests.cs index bf67721ea8..50e66f29e1 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_UsesNamespace() + { + 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.AttributeDbNamespace)); + Assert.Equal(0, result.GetTagValue(SemanticConventions.AttributeDbNamespace)); + } + + [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,85 +96,93 @@ 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)); - Assert.Equal("SET", result.GetTagValue(SemanticConventions.AttributeDbStatement)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbOperationName)); + Assert.Equal("SET", result.GetTagValue(SemanticConventions.AttributeDbOperationName)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbQueryText)); + Assert.Equal("SET", result.GetTagValue(SemanticConventions.AttributeDbQueryText)); } [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.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerPort)); - Assert.Equal(port, result.GetTagValue(SemanticConventions.AttributeNetPeerPort)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeServerAddress)); + Assert.Equal(addressString, result.GetTagValue(SemanticConventions.AttributeServerAddress)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeServerPort)); + Assert.Equal(port, result.GetTagValue(SemanticConventions.AttributeServerPort)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetworkPeerAddress)); + Assert.Equal(addressString, result.GetTagValue(SemanticConventions.AttributeNetworkPeerAddress)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetworkPeerPort)); + Assert.Equal(port, result.GetTagValue(SemanticConventions.AttributeNetworkPeerPort)); } [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)); - Assert.Equal(dnsEndPoint.Host, result.GetTagValue(SemanticConventions.AttributeNetPeerName)); - Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerPort)); - Assert.Equal(dnsEndPoint.Port, result.GetTagValue(SemanticConventions.AttributeNetPeerPort)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeServerAddress)); + Assert.Equal(dnsEndPoint.Host, result.GetTagValue(SemanticConventions.AttributeServerAddress)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeServerPort)); + Assert.Equal(dnsEndPoint.Port, result.GetTagValue(SemanticConventions.AttributeServerPort)); } #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)); - Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributePeerService)); + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeServerAddress)); + Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributeServerAddress)); + Assert.Null(result.GetTagValue(SemanticConventions.AttributeServerPort)); } #endif } diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs index 4ede2553fa..ba3c1dd8c8 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.AttributeDbNamespace)); if (endPoint is IPEndPoint ipEndPoint) {