1
1
#if NET6_0_OR_GREATER
2
2
3
+ using System ;
3
4
using System . Collections . Generic ;
5
+ using System . Diagnostics ;
4
6
using System . Diagnostics . Metrics ;
5
7
6
8
namespace StackExchange . Redis ;
7
9
8
10
internal sealed class RedisMetrics
9
11
{
12
+ private static readonly double s_tickFrequency = ( double ) TimeSpan . TicksPerSecond / Stopwatch . Frequency ;
13
+ // cache these boxed boolean values so we don't allocate on each usage.
14
+ private static readonly object s_trueBox = true ;
15
+ private static readonly object s_falseBox = false ;
16
+
10
17
private readonly Meter _meter ;
11
18
private readonly Counter < long > _operationCount ;
12
- private readonly Counter < long > _completedAsynchronously ;
13
- private readonly Counter < long > _completedSynchronously ;
14
- private readonly Counter < long > _failedAsynchronously ;
15
- private readonly Counter < long > _failedSynchronously ;
19
+ private readonly Histogram < double > _messageDuration ;
16
20
private readonly Counter < long > _nonPreferredEndpointCount ;
17
21
18
22
public static readonly RedisMetrics Instance = new RedisMetrics ( ) ;
@@ -22,67 +26,53 @@ private RedisMetrics()
22
26
_meter = new Meter ( "StackExchange.Redis" ) ;
23
27
24
28
_operationCount = _meter . CreateCounter < long > (
25
- "redis- operation- count" ,
29
+ "db. redis. operation. count" ,
26
30
description : "The number of operations performed." ) ;
27
31
28
- _completedAsynchronously = _meter . CreateCounter < long > (
29
- "redis-completed-asynchronously" ,
30
- description : "The number of operations that have been completed asynchronously." ) ;
31
-
32
- _completedSynchronously = _meter . CreateCounter < long > (
33
- "redis-completed-synchronously" ,
34
- description : "The number of operations that have been completed synchronously." ) ;
35
-
36
- _failedAsynchronously = _meter . CreateCounter < long > (
37
- "redis-failed-asynchronously" ,
38
- description : "The number of operations that failed to complete asynchronously." ) ;
39
-
40
- _failedSynchronously = _meter . CreateCounter < long > (
41
- "redis-failed-synchronously" ,
42
- description : "The number of operations that failed to complete synchronously." ) ;
32
+ _messageDuration = _meter . CreateHistogram < double > (
33
+ "db.redis.duration" ,
34
+ unit : "s" ,
35
+ description : "Measures the duration of outbound message requests." ) ;
43
36
44
37
_nonPreferredEndpointCount = _meter . CreateCounter < long > (
45
- "redis-non-preferred-endpoint- count" ,
38
+ "db. redis.non_preferred_endpoint. count" ,
46
39
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." ) ;
47
40
}
48
41
49
42
public void IncrementOperationCount ( string endpoint )
50
43
{
51
- if ( _operationCount . Enabled )
52
- {
53
- _operationCount . Add ( 1 ,
54
- new KeyValuePair < string , object ? > ( "endpoint" , endpoint ) ) ;
55
- }
44
+ _operationCount . Add ( 1 ,
45
+ new KeyValuePair < string , object ? > ( "endpoint" , endpoint ) ) ;
56
46
}
57
47
58
- public void OnMessageComplete ( IResultBox ? result )
48
+ public void OnMessageComplete ( Message message , IResultBox ? result )
59
49
{
60
- if ( result is not null &&
61
- ( _completedAsynchronously . Enabled ||
62
- _completedSynchronously . Enabled ||
63
- _failedAsynchronously . Enabled ||
64
- _failedSynchronously . Enabled ) )
50
+ // The caller ensures we can don't record on the same resultBox from two threads.
51
+ // 'result' can be null if this method is called for the same message more than once.
52
+ if ( result is not null && _messageDuration . Enabled )
65
53
{
66
- Counter < long > counter = ( result . IsFaulted , result . IsAsync ) switch
54
+ // Stopwatch.GetElapsedTime is only available in net7.0+
55
+ // https://github.com/dotnet/runtime/blob/ae068fec6ede58d2a5b343c5ac41c9ca8715fa47/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Stopwatch.cs#L129-L137
56
+ var now = Stopwatch . GetTimestamp ( ) ;
57
+ var duration = new TimeSpan ( ( long ) ( ( now - message . CreatedTimestamp ) * s_tickFrequency ) ) ;
58
+
59
+ var tags = new TagList
67
60
{
68
- ( false , true ) => _completedAsynchronously ,
69
- ( false , false ) => _completedSynchronously ,
70
- ( true , true ) => _failedAsynchronously ,
71
- ( true , false ) => _failedSynchronously ,
61
+ { "db.redis.async" , result . IsAsync ? s_trueBox : s_falseBox } ,
62
+ { "db.redis.faulted" , result . IsFaulted ? s_trueBox : s_falseBox }
63
+ // TODO: can we pass endpoint here?
64
+ // should we log the Db?
65
+ // { "db.redis.database_index", message.Db },
72
66
} ;
73
67
74
- // TODO: can we pass endpoint here?
75
- counter . Add ( 1 ) ;
68
+ _messageDuration . Record ( duration . TotalSeconds , tags ) ;
76
69
}
77
70
}
78
71
79
72
public void IncrementNonPreferredEndpointCount ( string endpoint )
80
73
{
81
- if ( _nonPreferredEndpointCount . Enabled )
82
- {
83
- _nonPreferredEndpointCount . Add ( 1 ,
84
- new KeyValuePair < string , object ? > ( "endpoint" , endpoint ) ) ;
85
- }
74
+ _nonPreferredEndpointCount . Add ( 1 ,
75
+ new KeyValuePair < string , object ? > ( "endpoint" , endpoint ) ) ;
86
76
}
87
77
}
88
78
0 commit comments