@@ -9,6 +9,7 @@ namespace ServiceControl.Transports.RabbitMQ;
9
9
using System . Net . Http ;
10
10
using System . Net . Http . Json ;
11
11
using System . Runtime . CompilerServices ;
12
+ using System . Text . Json ;
12
13
using System . Text . Json . Nodes ;
13
14
using System . Threading ;
14
15
using System . Threading . Tasks ;
@@ -88,15 +89,21 @@ protected override void InitializeCore(ReadOnlyDictionary<string, string> settin
88
89
89
90
if ( InitialiseErrors . Count == 0 )
90
91
{
91
- httpClient = new HttpClient ( new SocketsHttpHandler
92
- {
93
- Credentials = defaultCredential ,
94
- PooledConnectionLifetime = TimeSpan . FromMinutes ( 2 )
95
- } )
96
- { BaseAddress = new Uri ( apiUrl ) } ;
92
+ // ideally we would use the HttpClientFactory, but it would be a bit more involved to set that up
93
+ // so for now we are using a virtual method that can be overriden in tests
94
+ // https://github.com/Particular/ServiceControl/issues/4493
95
+ httpClient = CreateHttpClient ( defaultCredential , apiUrl ) ;
97
96
}
98
97
}
99
98
99
+ protected virtual HttpClient CreateHttpClient ( NetworkCredential defaultCredential , string apiUrl ) =>
100
+ new ( new SocketsHttpHandler
101
+ {
102
+ Credentials = defaultCredential ,
103
+ PooledConnectionLifetime = TimeSpan . FromMinutes ( 2 )
104
+ } )
105
+ { BaseAddress = new Uri ( apiUrl ) } ;
106
+
100
107
public override async IAsyncEnumerable < QueueThroughput > GetThroughputPerDay ( IBrokerQueue brokerQueue ,
101
108
DateOnly startDate ,
102
109
[ EnumeratorCancellation ] CancellationToken cancellationToken = default )
@@ -105,49 +112,22 @@ public override async IAsyncEnumerable<QueueThroughput> GetThroughputPerDay(IBro
105
112
var url = $ "/api/queues/{ HttpUtility . UrlEncode ( queue . VHost ) } /{ HttpUtility . UrlEncode ( queue . QueueName ) } ";
106
113
107
114
logger . LogDebug ( $ "Querying { url } ") ;
108
- var node = await pipeline . ExecuteAsync ( async token => await httpClient ! . GetFromJsonAsync < JsonNode > ( url , token ) , cancellationToken ) ;
109
- queue . AckedMessages = GetAck ( ) ;
115
+ var newReading = await pipeline . ExecuteAsync ( async token => new RabbitMQBrokerQueueDetails ( await httpClient ! . GetFromJsonAsync < JsonElement > ( url , token ) ) , cancellationToken ) ;
116
+ _ = queue . CalculateThroughputFrom ( newReading ) ;
110
117
111
118
// looping for 24hrs, in 4 increments of 15 minutes
112
119
for ( var i = 0 ; i < 24 * 4 ; i ++ )
113
120
{
114
- bool throughputSent = false ;
115
121
await Task . Delay ( TimeSpan . FromMinutes ( 15 ) , timeProvider , cancellationToken ) ;
116
122
logger . LogDebug ( $ "Querying { url } ") ;
117
- node = await pipeline . ExecuteAsync ( async token => await httpClient ! . GetFromJsonAsync < JsonNode > ( url , token ) , cancellationToken ) ;
118
- var newReading = GetAck ( ) ;
119
- if ( newReading is not null )
120
- {
121
- if ( newReading >= queue . AckedMessages )
122
- {
123
- yield return new QueueThroughput
124
- {
125
- DateUTC = DateOnly . FromDateTime ( timeProvider . GetUtcNow ( ) . DateTime ) ,
126
- TotalThroughput = newReading . Value - queue . AckedMessages . Value
127
- } ;
128
- throughputSent = true ;
129
- }
130
- queue . AckedMessages = newReading ;
131
- }
132
-
133
- if ( ! throughputSent )
134
- {
135
- yield return new QueueThroughput
136
- {
137
- DateUTC = DateOnly . FromDateTime ( timeProvider . GetUtcNow ( ) . DateTime ) ,
138
- TotalThroughput = 0
139
- } ;
140
- }
141
- }
142
- yield break ;
123
+ newReading = await pipeline . ExecuteAsync ( async token => new RabbitMQBrokerQueueDetails ( await httpClient ! . GetFromJsonAsync < JsonElement > ( url , token ) ) , cancellationToken ) ;
143
124
144
- long ? GetAck ( )
145
- {
146
- if ( node ! [ "message_stats" ] is JsonObject stats && stats [ "ack" ] is JsonValue val )
125
+ var newTotalThroughput = queue . CalculateThroughputFrom ( newReading ) ;
126
+ yield return new QueueThroughput
147
127
{
148
- return val . GetValue < long > ( ) ;
149
- }
150
- return null ;
128
+ DateUTC = DateOnly . FromDateTime ( timeProvider . GetUtcNow ( ) . DateTime ) ,
129
+ TotalThroughput = newTotalThroughput
130
+ } ;
151
131
}
152
132
}
153
133
@@ -266,7 +246,7 @@ async Task AddAdditionalQueueDetails(RabbitMQBrokerQueueDetails brokerQueue, Can
266
246
}
267
247
}
268
248
269
- async Task < ( RabbitMQBrokerQueueDetails [ ] ? , bool morePages ) > GetPage ( int page , CancellationToken cancellationToken )
249
+ public async Task < ( RabbitMQBrokerQueueDetails [ ] ? , bool morePages ) > GetPage ( int page , CancellationToken cancellationToken )
270
250
{
271
251
var url = $ "/api/queues/{ HttpUtility . UrlEncode ( connectionConfiguration . VirtualHost ) } ?page={ page } &page_size=500&name=&use_regex=false&pagination=true";
272
252
@@ -283,22 +263,27 @@ async Task AddAdditionalQueueDetails(RabbitMQBrokerQueueDetails brokerQueue, Can
283
263
return ( null , false ) ;
284
264
}
285
265
286
- var queues = items . Select ( item => new RabbitMQBrokerQueueDetails ( item ! ) ) . ToArray ( ) ;
287
-
288
- return ( queues , pageCount > pageReturned ) ;
266
+ return ( MaterializeQueueDetails ( items ) , pageCount > pageReturned ) ;
289
267
}
290
268
// Older versions of RabbitMQ API did not have paging and returned the array of items directly
291
269
case JsonArray arr :
292
270
{
293
- var queues = arr . Select ( item => new RabbitMQBrokerQueueDetails ( item ! ) ) . ToArray ( ) ;
294
-
295
- return ( queues , false ) ;
271
+ return ( MaterializeQueueDetails ( arr ) , false ) ;
296
272
}
297
273
default :
298
274
throw new Exception ( "Was not able to get list of queues from RabbitMQ broker." ) ;
299
275
}
300
276
}
301
277
278
+ static RabbitMQBrokerQueueDetails [ ] MaterializeQueueDetails ( JsonArray items )
279
+ {
280
+ // It is not possible to directly operated on the JsonNode. When the JsonNode is a JObject
281
+ // and the indexer is access the internal dictionary is initialized which can cause key not found exceptions
282
+ // when the payload contains the same key multiple times (which happened in the past).
283
+ var queues = items . Select ( item => new RabbitMQBrokerQueueDetails ( item ! . Deserialize < JsonElement > ( ) ) ) . ToArray ( ) ;
284
+ return queues ;
285
+ }
286
+
302
287
public override KeyDescriptionPair [ ] Settings =>
303
288
[
304
289
new KeyDescriptionPair ( RabbitMQSettings . API , RabbitMQSettings . APIDescription ) ,
0 commit comments