From 8dd98fa04d19cc0916add4dba3dd101624f79e4f Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 29 Jun 2023 17:58:37 -0500 Subject: [PATCH 1/5] Initial Metrics implementations --- src/StackExchange.Redis/PhysicalBridge.cs | 4 ++ src/StackExchange.Redis/RedisMetrics.cs | 49 +++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/StackExchange.Redis/RedisMetrics.cs diff --git a/src/StackExchange.Redis/PhysicalBridge.cs b/src/StackExchange.Redis/PhysicalBridge.cs index 68ea70105..8d64f8859 100644 --- a/src/StackExchange.Redis/PhysicalBridge.cs +++ b/src/StackExchange.Redis/PhysicalBridge.cs @@ -350,6 +350,10 @@ internal string GetStormLog() internal void IncrementOpCount() { Interlocked.Increment(ref operationCount); + +#if NET6_0_OR_GREATER + RedisMetrics.Instance.IncrementOpCount(Name); +#endif } internal void KeepAlive() diff --git a/src/StackExchange.Redis/RedisMetrics.cs b/src/StackExchange.Redis/RedisMetrics.cs new file mode 100644 index 000000000..8bfea6adb --- /dev/null +++ b/src/StackExchange.Redis/RedisMetrics.cs @@ -0,0 +1,49 @@ +#if NET6_0_OR_GREATER + +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace StackExchange.Redis; + +internal sealed class RedisMetrics +{ + private readonly Meter _meter; + private readonly Counter _operationCount; + //private readonly Counter _completedAsynchronously; + //private readonly Counter _completedSynchronously; + //private readonly Counter _failedSynchronously; + + public static readonly RedisMetrics Instance = new RedisMetrics(); + + public RedisMetrics() + { + _meter = new Meter("StackExchange.Redis"); + + _operationCount = _meter.CreateCounter( + "operation-count", + description: "The number of operations performed."); + + //_completedAsynchronously = _meter.CreateCounter( + // "completed-asynchronously", + // description: "The number of operations that have been completed asynchronously."); + + //_completedSynchronously = _meter.CreateCounter( + // "completed-synchronously", + // description: "The number of operations that have been completed synchronously."); + + //_failedSynchronously = _meter.CreateCounter( + // "failed-synchronously", + // description: "The number of operations that failed to complete asynchronously."); + } + + public void IncrementOpCount(string connectionName) + { + if (_operationCount.Enabled) + { + _operationCount.Add(1, + new KeyValuePair("connection-name", connectionName)); + } + } +} + +#endif From fbafc9d28f5cc1031bcb16f622353beb9d743586 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 30 Jun 2023 13:40:47 -0500 Subject: [PATCH 2/5] Add NonPreferredEndpointCount --- src/StackExchange.Redis/PhysicalBridge.cs | 13 ++++++++++-- src/StackExchange.Redis/RedisMetrics.cs | 26 +++++++++++++++++------ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/StackExchange.Redis/PhysicalBridge.cs b/src/StackExchange.Redis/PhysicalBridge.cs index 8d64f8859..4438ba1b6 100644 --- a/src/StackExchange.Redis/PhysicalBridge.cs +++ b/src/StackExchange.Redis/PhysicalBridge.cs @@ -350,9 +350,8 @@ internal string GetStormLog() internal void IncrementOpCount() { Interlocked.Increment(ref operationCount); - #if NET6_0_OR_GREATER - RedisMetrics.Instance.IncrementOpCount(Name); + RedisMetrics.Instance.IncrementOperationCount(Name); #endif } @@ -1408,12 +1407,22 @@ private void LogNonPreferred(CommandFlags flags, bool isReplica) if (isReplica) { if (Message.GetPrimaryReplicaFlags(flags) == CommandFlags.PreferMaster) + { Interlocked.Increment(ref nonPreferredEndpointCount); +#if NET6_0_OR_GREATER + RedisMetrics.Instance.IncrementNonPreferredEndpointCount(Name); +#endif + } } else { if (Message.GetPrimaryReplicaFlags(flags) == CommandFlags.PreferReplica) + { Interlocked.Increment(ref nonPreferredEndpointCount); +#if NET6_0_OR_GREATER + RedisMetrics.Instance.IncrementNonPreferredEndpointCount(Name); +#endif + } } } } diff --git a/src/StackExchange.Redis/RedisMetrics.cs b/src/StackExchange.Redis/RedisMetrics.cs index 8bfea6adb..cf4455b56 100644 --- a/src/StackExchange.Redis/RedisMetrics.cs +++ b/src/StackExchange.Redis/RedisMetrics.cs @@ -12,6 +12,7 @@ internal sealed class RedisMetrics //private readonly Counter _completedAsynchronously; //private readonly Counter _completedSynchronously; //private readonly Counter _failedSynchronously; + private readonly Counter _nonPreferredEndpointCount; public static readonly RedisMetrics Instance = new RedisMetrics(); @@ -20,28 +21,41 @@ public RedisMetrics() _meter = new Meter("StackExchange.Redis"); _operationCount = _meter.CreateCounter( - "operation-count", + "redis-operation-count", description: "The number of operations performed."); //_completedAsynchronously = _meter.CreateCounter( - // "completed-asynchronously", + // "redis-completed-asynchronously", // description: "The number of operations that have been completed asynchronously."); //_completedSynchronously = _meter.CreateCounter( - // "completed-synchronously", + // "redis-completed-synchronously", // description: "The number of operations that have been completed synchronously."); //_failedSynchronously = _meter.CreateCounter( - // "failed-synchronously", + // "redis-failed-synchronously", // description: "The number of operations that failed to complete asynchronously."); + + _nonPreferredEndpointCount = _meter.CreateCounter( + "redis-non-preferred-endpoint-count", + description: "Indicates the total number of messages dispatched to a non-preferred endpoint, for example sent to a primary when the caller stated a preference of replica."); } - public void IncrementOpCount(string connectionName) + public void IncrementOperationCount(string endpoint) { if (_operationCount.Enabled) { _operationCount.Add(1, - new KeyValuePair("connection-name", connectionName)); + new KeyValuePair("endpoint", endpoint)); + } + } + + public void IncrementNonPreferredEndpointCount(string endpoint) + { + if (_nonPreferredEndpointCount.Enabled) + { + _nonPreferredEndpointCount.Add(1, + new KeyValuePair("endpoint", endpoint)); } } } From 8837b215f559796a218d0304d85f3e1041ed1b76 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 30 Jun 2023 15:41:07 -0500 Subject: [PATCH 3/5] Implement completed failed counters. --- src/StackExchange.Redis/Message.cs | 5 ++- src/StackExchange.Redis/RedisMetrics.cs | 52 ++++++++++++++++++------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs index a76001756..1964699dd 100644 --- a/src/StackExchange.Redis/Message.cs +++ b/src/StackExchange.Redis/Message.cs @@ -374,6 +374,9 @@ public void Complete() // set the completion/performance data performance?.SetCompleted(); +#if NET6_0_OR_GREATER + RedisMetrics.Instance.OnMessageComplete(currBox); +#endif currBox?.ActivateContinuations(); } @@ -441,7 +444,7 @@ internal static Message Create(int db, CommandFlags flags, RedisCommand command, 3 => new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2]), 4 => new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3]), 5 => new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4]), - 6 => new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3],values[4],values[5]), + 6 => new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5]), 7 => new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5], values[6]), _ => new CommandKeyKeyValuesMessage(db, flags, command, key0, key1, values), }; diff --git a/src/StackExchange.Redis/RedisMetrics.cs b/src/StackExchange.Redis/RedisMetrics.cs index cf4455b56..e40a22385 100644 --- a/src/StackExchange.Redis/RedisMetrics.cs +++ b/src/StackExchange.Redis/RedisMetrics.cs @@ -9,14 +9,15 @@ internal sealed class RedisMetrics { private readonly Meter _meter; private readonly Counter _operationCount; - //private readonly Counter _completedAsynchronously; - //private readonly Counter _completedSynchronously; - //private readonly Counter _failedSynchronously; + private readonly Counter _completedAsynchronously; + private readonly Counter _completedSynchronously; + private readonly Counter _failedAsynchronously; + private readonly Counter _failedSynchronously; private readonly Counter _nonPreferredEndpointCount; public static readonly RedisMetrics Instance = new RedisMetrics(); - public RedisMetrics() + private RedisMetrics() { _meter = new Meter("StackExchange.Redis"); @@ -24,17 +25,21 @@ public RedisMetrics() "redis-operation-count", description: "The number of operations performed."); - //_completedAsynchronously = _meter.CreateCounter( - // "redis-completed-asynchronously", - // description: "The number of operations that have been completed asynchronously."); + _completedAsynchronously = _meter.CreateCounter( + "redis-completed-asynchronously", + description: "The number of operations that have been completed asynchronously."); - //_completedSynchronously = _meter.CreateCounter( - // "redis-completed-synchronously", - // description: "The number of operations that have been completed synchronously."); + _completedSynchronously = _meter.CreateCounter( + "redis-completed-synchronously", + description: "The number of operations that have been completed synchronously."); - //_failedSynchronously = _meter.CreateCounter( - // "redis-failed-synchronously", - // description: "The number of operations that failed to complete asynchronously."); + _failedAsynchronously = _meter.CreateCounter( + "redis-failed-asynchronously", + description: "The number of operations that failed to complete asynchronously."); + + _failedSynchronously = _meter.CreateCounter( + "redis-failed-synchronously", + description: "The number of operations that failed to complete synchronously."); _nonPreferredEndpointCount = _meter.CreateCounter( "redis-non-preferred-endpoint-count", @@ -50,6 +55,27 @@ public void IncrementOperationCount(string endpoint) } } + public void OnMessageComplete(IResultBox? result) + { + if (result is not null && + (_completedAsynchronously.Enabled || + _completedSynchronously.Enabled || + _failedAsynchronously.Enabled || + _failedSynchronously.Enabled)) + { + Counter counter = (result.IsFaulted, result.IsAsync) switch + { + (false, true) => _completedAsynchronously, + (false, false) => _completedSynchronously, + (true, true) => _failedAsynchronously, + (true, false) => _failedSynchronously, + }; + + // TODO: can we pass endpoint here? + counter.Add(1); + } + } + public void IncrementNonPreferredEndpointCount(string endpoint) { if (_nonPreferredEndpointCount.Enabled) From a9262186df3f476a336abb19718fcd8048c4a6c3 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 14 Jul 2023 18:24:15 -0500 Subject: [PATCH 4/5] Rename the metrics following OTel naming - no dashes. - use `db.redis` prefix Implement duration histogram and collapse 4 counters into it. --- src/StackExchange.Redis/Message.cs | 2 +- src/StackExchange.Redis/RedisMetrics.cs | 78 +++++++++++-------------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs index 1964699dd..57dcd9a21 100644 --- a/src/StackExchange.Redis/Message.cs +++ b/src/StackExchange.Redis/Message.cs @@ -375,7 +375,7 @@ public void Complete() // set the completion/performance data performance?.SetCompleted(); #if NET6_0_OR_GREATER - RedisMetrics.Instance.OnMessageComplete(currBox); + RedisMetrics.Instance.OnMessageComplete(this, currBox); #endif currBox?.ActivateContinuations(); diff --git a/src/StackExchange.Redis/RedisMetrics.cs b/src/StackExchange.Redis/RedisMetrics.cs index e40a22385..8e541703b 100644 --- a/src/StackExchange.Redis/RedisMetrics.cs +++ b/src/StackExchange.Redis/RedisMetrics.cs @@ -1,18 +1,22 @@ #if NET6_0_OR_GREATER +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Metrics; namespace StackExchange.Redis; internal sealed class RedisMetrics { + private static readonly double s_tickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + // cache these boxed boolean values so we don't allocate on each usage. + private static readonly object s_trueBox = true; + private static readonly object s_falseBox = false; + private readonly Meter _meter; private readonly Counter _operationCount; - private readonly Counter _completedAsynchronously; - private readonly Counter _completedSynchronously; - private readonly Counter _failedAsynchronously; - private readonly Counter _failedSynchronously; + private readonly Histogram _messageDuration; private readonly Counter _nonPreferredEndpointCount; public static readonly RedisMetrics Instance = new RedisMetrics(); @@ -22,67 +26,53 @@ private RedisMetrics() _meter = new Meter("StackExchange.Redis"); _operationCount = _meter.CreateCounter( - "redis-operation-count", + "db.redis.operation.count", description: "The number of operations performed."); - _completedAsynchronously = _meter.CreateCounter( - "redis-completed-asynchronously", - description: "The number of operations that have been completed asynchronously."); - - _completedSynchronously = _meter.CreateCounter( - "redis-completed-synchronously", - description: "The number of operations that have been completed synchronously."); - - _failedAsynchronously = _meter.CreateCounter( - "redis-failed-asynchronously", - description: "The number of operations that failed to complete asynchronously."); - - _failedSynchronously = _meter.CreateCounter( - "redis-failed-synchronously", - description: "The number of operations that failed to complete synchronously."); + _messageDuration = _meter.CreateHistogram( + "db.redis.duration", + unit: "s", + description: "Measures the duration of outbound message requests."); _nonPreferredEndpointCount = _meter.CreateCounter( - "redis-non-preferred-endpoint-count", + "db.redis.non_preferred_endpoint.count", description: "Indicates the total number of messages dispatched to a non-preferred endpoint, for example sent to a primary when the caller stated a preference of replica."); } public void IncrementOperationCount(string endpoint) { - if (_operationCount.Enabled) - { - _operationCount.Add(1, - new KeyValuePair("endpoint", endpoint)); - } + _operationCount.Add(1, + new KeyValuePair("endpoint", endpoint)); } - public void OnMessageComplete(IResultBox? result) + public void OnMessageComplete(Message message, IResultBox? result) { - if (result is not null && - (_completedAsynchronously.Enabled || - _completedSynchronously.Enabled || - _failedAsynchronously.Enabled || - _failedSynchronously.Enabled)) + // The caller ensures we can don't record on the same resultBox from two threads. + // 'result' can be null if this method is called for the same message more than once. + if (result is not null && _messageDuration.Enabled) { - Counter counter = (result.IsFaulted, result.IsAsync) switch + // Stopwatch.GetElapsedTime is only available in net7.0+ + // https://github.com/dotnet/runtime/blob/ae068fec6ede58d2a5b343c5ac41c9ca8715fa47/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Stopwatch.cs#L129-L137 + var now = Stopwatch.GetTimestamp(); + var duration = new TimeSpan((long)((now - message.CreatedTimestamp) * s_tickFrequency)); + + var tags = new TagList { - (false, true) => _completedAsynchronously, - (false, false) => _completedSynchronously, - (true, true) => _failedAsynchronously, - (true, false) => _failedSynchronously, + { "db.redis.async", result.IsAsync ? s_trueBox : s_falseBox }, + { "db.redis.faulted", result.IsFaulted ? s_trueBox : s_falseBox } + // TODO: can we pass endpoint here? + // should we log the Db? + // { "db.redis.database_index", message.Db }, }; - // TODO: can we pass endpoint here? - counter.Add(1); + _messageDuration.Record(duration.TotalSeconds, tags); } } public void IncrementNonPreferredEndpointCount(string endpoint) { - if (_nonPreferredEndpointCount.Enabled) - { - _nonPreferredEndpointCount.Add(1, - new KeyValuePair("endpoint", endpoint)); - } + _nonPreferredEndpointCount.Add(1, + new KeyValuePair("endpoint", endpoint)); } } From 837345f9d4ec51ff9fff541c271271db1b67a876 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 18 Jul 2023 18:06:01 -0500 Subject: [PATCH 5/5] Refactor RedisMetrics to be instance based and allow tests to inject the Meter object. --- Directory.Packages.props | 1 + .../ConfigurationOptions.cs | 12 +++++ .../ConnectionMultiplexer.Metrics.cs | 19 ++++++++ .../ConnectionMultiplexer.cs | 6 +++ src/StackExchange.Redis/Message.cs | 13 +++++- src/StackExchange.Redis/PhysicalBridge.cs | 6 +-- src/StackExchange.Redis/RedisMetrics.cs | 6 +-- .../StackExchange.Redis.Tests/MetricsTests.cs | 44 +++++++++++++++++++ .../StackExchange.Redis.Tests.csproj | 4 ++ 9 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/StackExchange.Redis/ConnectionMultiplexer.Metrics.cs create mode 100644 tests/StackExchange.Redis.Tests/MetricsTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 309672642..9d5b24dca 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,6 +12,7 @@ + diff --git a/src/StackExchange.Redis/ConfigurationOptions.cs b/src/StackExchange.Redis/ConfigurationOptions.cs index 19314c344..860532c00 100644 --- a/src/StackExchange.Redis/ConfigurationOptions.cs +++ b/src/StackExchange.Redis/ConfigurationOptions.cs @@ -12,6 +12,10 @@ using System.Threading.Tasks; using StackExchange.Redis.Configuration; +#if NET6_0_OR_GREATER +using System.Diagnostics.Metrics; +#endif + namespace StackExchange.Redis { /// @@ -619,6 +623,14 @@ public int ConfigCheckSeconds set => configCheckSeconds = value; } +#if NET6_0_OR_GREATER + /// + /// A factory method able to inject the object to use + /// when emitting metrics. Used by tests. + /// + internal Func? MeterFactory { get; set; } +#endif + /// /// Parse the configuration from a comma-delimited configuration string. /// diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.Metrics.cs b/src/StackExchange.Redis/ConnectionMultiplexer.Metrics.cs new file mode 100644 index 000000000..97f580224 --- /dev/null +++ b/src/StackExchange.Redis/ConnectionMultiplexer.Metrics.cs @@ -0,0 +1,19 @@ +#if NET6_0_OR_GREATER + +namespace StackExchange.Redis; + +public partial class ConnectionMultiplexer +{ + internal RedisMetrics Metrics { get; } + + private static RedisMetrics GetMetrics(ConfigurationOptions configuration) + { + if (configuration.MeterFactory is not null) + { + return new RedisMetrics(configuration.MeterFactory()); + } + + return RedisMetrics.Default; + } +} +#endif diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index 68d58253a..988632bef 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -128,6 +128,9 @@ static ConnectionMultiplexer() private ConnectionMultiplexer(ConfigurationOptions configuration, ServerType? serverType = null, EndPointCollection? endpoints = null) { RawConfig = configuration ?? throw new ArgumentNullException(nameof(configuration)); +#if NET6_0_OR_GREATER + Metrics = GetMetrics(configuration); +#endif EndPoints = endpoints ?? RawConfig.EndPoints.Clone(); EndPoints.SetDefaultPorts(serverType, ssl: RawConfig.Ssl); @@ -1939,6 +1942,9 @@ internal void UpdateClusterRange(ClusterConfiguration configuration) private bool PrepareToPushMessageToBridge(Message message, ResultProcessor? processor, IResultBox? resultBox, [NotNullWhen(true)] ref ServerEndPoint? server) { message.SetSource(processor, resultBox); +#if NET6_0_OR_GREATER + message.SetMetrics(Metrics); +#endif if (server == null) { diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs index 57dcd9a21..d3e57eb5f 100644 --- a/src/StackExchange.Redis/Message.cs +++ b/src/StackExchange.Redis/Message.cs @@ -84,6 +84,10 @@ internal abstract class Message : ICompletable internal DateTime CreatedDateTime; internal long CreatedTimestamp; +#if NET6_0_OR_GREATER + private RedisMetrics? metrics; +#endif + protected Message(int db, CommandFlags flags, RedisCommand command) { bool dbNeeded = RequiresDatabase(command); @@ -134,6 +138,13 @@ internal void SetPrimaryOnly() } } +#if NET6_0_OR_GREATER + internal void SetMetrics(RedisMetrics redisMetrics) + { + metrics = redisMetrics; + } +#endif + internal void SetProfileStorage(ProfiledCommand storage) { performance = storage; @@ -375,7 +386,7 @@ public void Complete() // set the completion/performance data performance?.SetCompleted(); #if NET6_0_OR_GREATER - RedisMetrics.Instance.OnMessageComplete(this, currBox); + metrics?.OnMessageComplete(this, currBox); #endif currBox?.ActivateContinuations(); diff --git a/src/StackExchange.Redis/PhysicalBridge.cs b/src/StackExchange.Redis/PhysicalBridge.cs index 4438ba1b6..209b474c0 100644 --- a/src/StackExchange.Redis/PhysicalBridge.cs +++ b/src/StackExchange.Redis/PhysicalBridge.cs @@ -351,7 +351,7 @@ internal void IncrementOpCount() { Interlocked.Increment(ref operationCount); #if NET6_0_OR_GREATER - RedisMetrics.Instance.IncrementOperationCount(Name); + Multiplexer.Metrics.IncrementOperationCount(Name); #endif } @@ -1410,7 +1410,7 @@ private void LogNonPreferred(CommandFlags flags, bool isReplica) { Interlocked.Increment(ref nonPreferredEndpointCount); #if NET6_0_OR_GREATER - RedisMetrics.Instance.IncrementNonPreferredEndpointCount(Name); + Multiplexer.Metrics.IncrementNonPreferredEndpointCount(Name); #endif } } @@ -1420,7 +1420,7 @@ private void LogNonPreferred(CommandFlags flags, bool isReplica) { Interlocked.Increment(ref nonPreferredEndpointCount); #if NET6_0_OR_GREATER - RedisMetrics.Instance.IncrementNonPreferredEndpointCount(Name); + Multiplexer.Metrics.IncrementNonPreferredEndpointCount(Name); #endif } } diff --git a/src/StackExchange.Redis/RedisMetrics.cs b/src/StackExchange.Redis/RedisMetrics.cs index 8e541703b..cb2561804 100644 --- a/src/StackExchange.Redis/RedisMetrics.cs +++ b/src/StackExchange.Redis/RedisMetrics.cs @@ -19,11 +19,11 @@ internal sealed class RedisMetrics private readonly Histogram _messageDuration; private readonly Counter _nonPreferredEndpointCount; - public static readonly RedisMetrics Instance = new RedisMetrics(); + public static readonly RedisMetrics Default = new RedisMetrics(); - private RedisMetrics() + public RedisMetrics(Meter? meter = null) { - _meter = new Meter("StackExchange.Redis"); + _meter = meter ?? new Meter("StackExchange.Redis"); _operationCount = _meter.CreateCounter( "db.redis.operation.count", diff --git a/tests/StackExchange.Redis.Tests/MetricsTests.cs b/tests/StackExchange.Redis.Tests/MetricsTests.cs new file mode 100644 index 000000000..6824bc025 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/MetricsTests.cs @@ -0,0 +1,44 @@ +#if NET6_0_OR_GREATER +#pragma warning disable TBD // MetricCollector is for evaluation purposes only and is subject to change or removal in future updates. + +using Microsoft.Extensions.Telemetry.Testing.Metering; +using System.Diagnostics.Metrics; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace StackExchange.Redis.Tests; + +public class MetricsTests : TestBase +{ + public MetricsTests(ITestOutputHelper output) : base(output) { } + + [Fact] + public async Task SimpleCommandDuration() + { + var options = ConfigurationOptions.Parse(GetConfiguration()); + + using var meter = new Meter("StackExchange.Redis.Tests"); + using var collector = new MetricCollector(meter, "db.redis.duration"); + + options.MeterFactory = () => meter; + + using var conn = await ConnectionMultiplexer.ConnectAsync(options, Writer); + var db = conn.GetDatabase(); + + RedisKey key = Me(); + string? g1 = await db.StringGetAsync(key); + Assert.Null(g1); + + await collector.WaitForMeasurementsAsync(1); + + Assert.Collection(collector.GetMeasurementSnapshot(), + measurement => + { + // Built-in + Assert.Equal(true, measurement.Tags["db.redis.async"]); + Assert.Equal(false, measurement.Tags["db.redis.faulted"]); + }); + } +} +#endif diff --git a/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj b/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj index 112513a85..9be4e94fb 100644 --- a/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj +++ b/tests/StackExchange.Redis.Tests/StackExchange.Redis.Tests.csproj @@ -33,4 +33,8 @@ + + + +