@@ -22,57 +22,103 @@ module CacheMetrics =
22
22
let creations = Meter.CreateCounter< int64>( " creations" , " count" )
23
23
let disposals = Meter.CreateCounter< int64>( " disposals" , " count" )
24
24
25
- let mkTag name = KeyValuePair<_, obj>( " name" , name)
26
-
27
- let Add ( tag : KeyValuePair < _ , _ >) = adds.Add( 1 L, tag)
28
- let Update ( tag : KeyValuePair < _ , _ >) = updates.Add( 1 L, tag)
29
- let Hit ( tag : KeyValuePair < _ , _ >) = hits.Add( 1 L, tag)
30
- let Miss ( tag : KeyValuePair < _ , _ >) = misses.Add( 1 L, tag)
31
- let Eviction ( tag : KeyValuePair < _ , _ >) = evictions.Add( 1 L, tag)
32
- let EvictionFail ( tag : KeyValuePair < _ , _ >) = evictionFails.Add( 1 L, tag)
33
- let Created ( tag : KeyValuePair < _ , _ >) = creations.Add( 1 L, tag)
34
- let Disposed ( tag : KeyValuePair < _ , _ >) = disposals.Add( 1 L, tag)
35
-
36
- // Currently the Cache emits telemetry for raw cache events: hits, misses, evictions etc.
37
- // This class observes those counters and keeps a snapshot of readings. It is used in tests and can be used to print cache stats in debug mode.
38
- type CacheMetricsListener ( tag ) =
39
- let totals = Map [ for counter in CacheMetrics.allCounters -> counter.Name, ref 0 L ]
40
-
41
- let incr key v =
42
- Interlocked.Add( totals[ key], v) |> ignore
43
-
44
- let total key = totals[ key]. Value
45
-
46
- let mutable ratio = Double.NaN
25
+ let mutable private nextCacheId = 0
26
+
27
+ let mkTags ( name : string ) =
28
+ let cacheId = Interlocked.Increment & nextCacheId
29
+ [| " name" , box name; " cacheId" , box cacheId |]
30
+ |> Array.map KeyValuePair
31
+ |> TagList
32
+
33
+ let Add ( tags : inref < TagList >) = adds.Add( 1 L, & tags)
34
+ let Update ( tags : inref < TagList >) = updates.Add( 1 L, & tags)
35
+ let Hit ( tags : inref < TagList >) = hits.Add( 1 L, & tags)
36
+ let Miss ( tags : inref < TagList >) = misses.Add( 1 L, & tags)
37
+ let Eviction ( tags : inref < TagList >) = evictions.Add( 1 L, & tags)
38
+ let EvictionFail ( tags : inref < TagList >) = evictionFails.Add( 1 L, & tags)
39
+ let Created ( tags : inref < TagList >) = creations.Add( 1 L, & tags)
40
+ let Disposed ( tags : inref < TagList >) = disposals.Add( 1 L, & tags)
41
+
42
+ type Stats () =
43
+ let totals = Map [ for counter in allCounters -> counter.Name, ref 0 L ]
44
+ let total key = totals[ key]. Value
45
+
46
+ let mutable ratio = Double.NaN
47
+
48
+ let updateRatio () =
49
+ ratio <-
50
+ float ( total hits.Name)
51
+ / float ( total hits.Name + total misses.Name)
52
+
53
+ member _.Incr key v =
54
+ assert ( totals.ContainsKey key)
55
+ Interlocked.Add( totals[ key], v) |> ignore
56
+
57
+ if key = hits.Name || key = misses.Name then
58
+ updateRatio ()
59
+
60
+ member _.GetTotals () =
61
+ [ for k in totals.Keys -> k, total k ] |> Map.ofList
62
+
63
+ member _.Ratio = ratio
64
+
65
+ override _.ToString () =
66
+ let parts =
67
+ [ for kv in totals do
68
+ yield $" {kv.Key}={kv.Value.Value}"
69
+ if not ( Double.IsNaN ratio) then
70
+ yield $" hit-ratio={ratio:P2}" ]
71
+ String.Join( " , " , parts)
72
+
73
+ let statsByName = ConcurrentDictionary< string, Stats>()
74
+
75
+ let getStatsByName name = statsByName.GetOrAdd( name, fun _ -> Stats ())
76
+
77
+ let ListenToAll () =
78
+ let listener = new MeterListener()
79
+ for instrument in allCounters do
80
+ listener.EnableMeasurementEvents instrument
81
+ listener.SetMeasurementEventCallback( fun instrument v tags _ ->
82
+ match tags[ 0 ]. Value with
83
+ | :? string as name ->
84
+ let stats = getStatsByName name
85
+ stats.Incr instrument.Name v
86
+ | _ -> assert false )
87
+ listener.Start()
47
88
48
- let updateRatio () =
49
- ratio <-
50
- float ( total CacheMetrics.hits.Name)
51
- / float ( total CacheMetrics.hits.Name + total CacheMetrics.misses.Name)
89
+ let StatsToString () =
90
+ let sb = Text.StringBuilder()
91
+ sb.AppendLine " Cache Metrics:" |> ignore
92
+ for kv in statsByName do
93
+ sb.AppendLine $" Cache {kv.Key}: {kv.Value}" |> ignore
94
+ sb.AppendLine() |> ignore
95
+ string sb
52
96
53
- let listener = new MeterListener()
97
+ // Currently the Cache emits telemetry for raw cache events: hits, misses, evictions etc.
98
+ // This type observes those counters and keeps a snapshot of readings. It is used in tests and can be used to print cache stats in debug mode.
99
+ type CacheMetricsListener ( cacheTags : TagList ) =
54
100
55
- do
101
+ let stats = Stats()
102
+ let listener = new MeterListener()
56
103
57
- for instrument in CacheMetrics.allCounters do
58
- listener.EnableMeasurementEvents instrument
104
+ do
105
+ for instrument in allCounters do
106
+ listener.EnableMeasurementEvents instrument
59
107
60
- listener.SetMeasurementEventCallback( fun instrument v tags _ ->
61
- if tags[ 0 ] = tag then
62
- incr instrument.Name v
108
+ listener.SetMeasurementEventCallback( fun instrument v tags _ ->
109
+ let tagsMatch = tags[ 0 ] = cacheTags [ 0 ] && tags [ 1 ] = cacheTags [ 1 ]
110
+ if tagsMatch then stats.Incr instrument.Name v)
63
111
64
- if instrument = CacheMetrics.hits || instrument = CacheMetrics.misses then
65
- updateRatio ())
112
+ listener.Start()
66
113
67
- listener.Start()
114
+ interface IDisposable with
115
+ member _.Dispose () = listener.Dispose()
68
116
69
- interface IDisposable with
70
- member _.Dispose () = listener.Dispose()
117
+ member _.GetTotals () = stats.GetTotals()
71
118
72
- member _.GetTotals () =
73
- [ for k in totals.Keys -> k, total k ] |> Map.ofList
119
+ member _.Ratio = stats.Ratio
74
120
75
- member _.GetStats () = [ " hit-ratio " , ratio ] |> Map.ofList
121
+ override _.ToString () = stats.ToString ()
76
122
77
123
[<RequireQualifiedAccess>]
78
124
type EvictionMode =
@@ -163,7 +209,7 @@ type EvictionQueueMessage<'Entity, 'Target> =
163
209
| Update of 'Entity
164
210
165
211
[<Sealed; NoComparison; NoEquality>]
166
- [<DebuggerDisplay( " {GetStats ()}" ) >]
212
+ [<DebuggerDisplay( " {DebugDisplay ()}" ) >]
167
213
type Cache < 'Key , 'Value when 'Key: not null > internal ( options : CacheOptions < 'Key >, ? name ) =
168
214
169
215
do
@@ -190,7 +236,7 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
190
236
let evicted = Event<_>()
191
237
let evictionFailed = Event<_>()
192
238
193
- let tag = CacheMetrics.mkTag name
239
+ let tags = CacheMetrics.mkTags name
194
240
195
241
// Track disposal state (0 = not disposed, 1 = disposed)
196
242
let mutable disposed = 0
@@ -223,10 +269,10 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
223
269
224
270
match store.TryRemove( first.Value.Key) with
225
271
| true , _ ->
226
- CacheMetrics.Eviction tag
272
+ CacheMetrics.Eviction & tags
227
273
evicted.Trigger()
228
274
| _ ->
229
- CacheMetrics.EvictionFail tag
275
+ CacheMetrics.EvictionFail & tags
230
276
evictionFailed.Trigger()
231
277
deadKeysCount <- deadKeysCount + 1
232
278
@@ -244,11 +290,14 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
244
290
let startEvictionProcessor ct =
245
291
MailboxProcessor.Start(
246
292
( fun mb ->
247
- async {
248
- while true do
293
+ let rec processNext () =
294
+ async {
249
295
let! message = mb.Receive()
250
296
processEvictionMessage message
251
- }),
297
+ return ! processNext ()
298
+ }
299
+
300
+ processNext ()),
252
301
ct
253
302
)
254
303
@@ -271,20 +320,24 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
271
320
272
321
post, dispose
273
322
274
- do CacheMetrics.Created tag
323
+ #if DEBUG
324
+ let debugListener = new CacheMetrics.CacheMetricsListener( tags)
325
+ #endif
326
+
327
+ do CacheMetrics.Created & tags
275
328
276
329
member val Evicted = evicted.Publish
277
330
member val EvictionFailed = evictionFailed.Publish
278
331
279
332
member _.TryGetValue ( key : 'Key , value : outref < 'Value >) =
280
333
match store.TryGetValue( key) with
281
334
| true , entity ->
282
- CacheMetrics.Hit tag
335
+ CacheMetrics.Hit & tags
283
336
post ( EvictionQueueMessage.Update entity)
284
337
value <- entity.Value
285
338
true
286
339
| _ ->
287
- CacheMetrics.Miss tag
340
+ CacheMetrics.Miss & tags
288
341
value <- Unchecked.defaultof< 'Value>
289
342
false
290
343
@@ -294,7 +347,7 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
294
347
let added = store.TryAdd( key, entity)
295
348
296
349
if added then
297
- CacheMetrics.Add tag
350
+ CacheMetrics.Add & tags
298
351
post ( EvictionQueueMessage.Add( entity, store))
299
352
300
353
added
@@ -311,11 +364,11 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
311
364
312
365
if wasMiss then
313
366
post ( EvictionQueueMessage.Add( result, store))
314
- CacheMetrics.Add tag
315
- CacheMetrics.Miss tag
367
+ CacheMetrics.Add & tags
368
+ CacheMetrics.Miss & tags
316
369
else
317
370
post ( EvictionQueueMessage.Update result)
318
- CacheMetrics.Hit tag
371
+ CacheMetrics.Hit & tags
319
372
320
373
result.Value
321
374
@@ -330,18 +383,18 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
330
383
331
384
// Returned value tells us if the entity was added or updated.
332
385
if Object.ReferenceEquals( addValue, result) then
333
- CacheMetrics.Add tag
386
+ CacheMetrics.Add & tags
334
387
post ( EvictionQueueMessage.Add( addValue, store))
335
388
else
336
- CacheMetrics.Update tag
389
+ CacheMetrics.Update & tags
337
390
post ( EvictionQueueMessage.Update result)
338
391
339
- member _.CreateMetricsListener () = new CacheMetricsListener( tag )
392
+ member _.CreateMetricsListener () = new CacheMetrics. CacheMetricsListener( tags )
340
393
341
394
member _.Dispose () =
342
395
if Interlocked.Exchange(& disposed, 1 ) = 0 then
343
396
disposeEvictionProcessor ()
344
- CacheMetrics.Disposed tag
397
+ CacheMetrics.Disposed & tags
345
398
346
399
interface IDisposable with
347
400
member this.Dispose () =
@@ -350,3 +403,7 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
350
403
351
404
// Finalizer to ensure eviction loop is cancelled if Dispose wasn't called.
352
405
override this.Finalize () = this.Dispose()
406
+
407
+ #if DEBUG
408
+ member _.DebugDisplay () = debugListener.ToString()
409
+ #endif
0 commit comments