Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Instrumentation.StackExchangeRedis] Metrics support #1982

Closed
Original file line number Diff line number Diff line change
Expand Up @@ -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<System.Diagnostics.Activity!, StackExchange.Redis.Profiling.IProfiledCommand!>?
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions.Enrich.set -> void
Expand All @@ -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<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, object! serviceKey, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, StackExchange.Redis.IConnectionMultiplexer? connection, object? serviceKey, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>? configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentation!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<System.IServiceProvider!, OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentation!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
OpenTelemetry.Metrics.StackExchangeRedisMeterProviderBuilderExtensions
OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions
static OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentation!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationExtensions.ConfigureRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<System.IServiceProvider!, OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentation!>! 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<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, object! serviceKey, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, string? name, StackExchange.Redis.IConnectionMultiplexer? connection, object? serviceKey, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>? configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.StackExchangeRedisTracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisInstrumentationOptions!>! configure) -> OpenTelemetry.Trace.TracerProviderBuilder!
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Comment on lines +8 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I see only db.client.operation.duration is supported.


## 1.9.0-beta.1

Released 2024-Jul-23
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
Kielek marked this conversation as resolved.
Show resolved Hide resolved

private readonly Meter meter;

public RedisMetrics()
{
this.meter = new Meter(InstrumentationName, InstrumentationVersion);

this.DurationHistogram = this.meter.CreateHistogram<double>(
"db.client.operation.duration",
unit: "s",
description: "Duration of database client operations.");
}

public static RedisMetrics Instance { get; } = new RedisMetrics();

public Histogram<double> DurationHistogram { get; }

public bool Enabled => this.DurationHistogram.Enabled;

public void Dispose()
{
this.meter.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Func<object, (string?, string?)>> MessageDataGetter = new(() =>
{
var profiledCommandType = Type.GetType("StackExchange.Redis.Profiling.ProfiledCommand, StackExchange.Redis", throwOnError: true)!;
Expand Down Expand Up @@ -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))
Expand All @@ -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 ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TagList tags = default;

Isn't this sufficient?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the result of default(TagList) is an empty TagList, am I wrong?

Here I would like to set a null object to be validated in every AddTag and so optimize the memory allocation

new TagList(StackExchangeRedisConnectionInstrumentation.CreationTags) :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creationtags is no longer needed, as it is already covered via Meter Tags.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you suggest removing the meter tags #1982 (comment), I will keep it

default(IList<KeyValuePair<string, object?>>);

if (activity.IsAllDataRequested)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth to keep this functionality. Checks should be cheaper than adding couple of tags.

{
// 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);
Expand All @@ -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);
Comment on lines +173 to +174
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Candidate for separate PR - update semantic convention to 1.27.0. At least for network tags.
Maybe it will be worth to merge it before this PR? Then the final user experience and process review could be better.

Note. net.peer.name is deprecated. It it not only problem here.


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);
Expand All @@ -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<IProfiledCommand> sessionCommands, StackExchangeRedisInstrumentationOptions options)
public static void DrainSession(
Activity? parentActivity,
IEnumerable<IProfiledCommand> sessionCommands,
RedisMetrics redisMetrics,
StackExchangeRedisInstrumentationOptions options)
{
foreach (var command in sessionCommands)
{
ProfilerCommandToActivity(parentActivity, command, options);
ProfilerCommandInstrument(parentActivity, command, redisMetrics, options);
}
}

Expand Down Expand Up @@ -224,4 +276,9 @@ public static void DrainSession(Activity? parentActivity, IEnumerable<IProfiledC

return null;
}

private static void Add(this IList<KeyValuePair<string, object?>> tags, string ket, object? value)
{
tags?.Add(new KeyValuePair<string, object?>(ket, value));
}
}
Loading
Loading