Caching is an important tool in your toolbox as a developer. When used correctly in can vastly improve performance. However don't introduce caching blindly, it is crucial to monitor your cache usage to see if your caching strategy works and provides the benefits you expect.
With .NET 7 this became a little bit easier, thanks to the introduction of MemoryCacheStatistics
that holds cache hits, misses, and estimated size for IMemoryCache.
You can get an instance of MemoryCacheStatistics
by calling GetCurrentStatistics()
on your IMemoryCache.
instance when the flag TrackStatistics
is enabled.
If you construct the IMemoryCache instance yourself you can do this by specifying it through the MemoryCacheOptions object:
var options = new MemoryCacheOptions() | |
{ | |
TrackStatistics = true, | |
}; | |
var cache = new MemoryCache(options); |
Or if you are using Dependency Injection, you can do this:
services.AddMemoryCache(options => options.TrackStatistics = true); |
Now I can create my own EventSource
implementation that reads the data from the MemoryCacheStatistics
:
[EventSource(Name = "Example.Caching")] | |
internal sealed class CachingEventSource : EventSource | |
{ | |
private IMemoryCache _memoryCache; | |
private DiagnosticCounter _cacheHitsCounter; | |
private DiagnosticCounter _cacheMissesCounter; | |
public CachingEventSource(IMemoryCache memoryCache) | |
{ | |
_memoryCache = memoryCache; | |
} | |
protected override void OnEventCommand(EventCommandEventArgs command) | |
{ | |
if (command.Command == EventCommand.Enable) | |
{ | |
var statistics = _memoryCache.GetCurrentStatistics(); | |
if (_cacheHitsCounter == null) | |
{ | |
_cacheHitsCounter = new PollingCounter("cache-hits", this, () => statistics.TotalHits) | |
{ | |
DisplayName = "Cache hits", | |
}; | |
} | |
if (_cacheMissesCounter == null) | |
{ | |
_cacheMissesCounter = new PollingCounter("cache-misses", this, () => statistics.TotalMisses) | |
{ | |
DisplayName = "Cache misses", | |
}; | |
} | |
} | |
} | |
} |
I created a small console application that reads and writes some value to an IMemoryCache
instance and uses this EventSource:
var options = new MemoryCacheOptions() | |
{ | |
TrackStatistics = true, | |
}; | |
var cache = new MemoryCache(options); | |
CachingEventSource cachingEventSource = new (cache); | |
Console.WriteLine("Enter a value and hit 'enter'"); | |
while (true) | |
{ | |
//Read a value from the console | |
//If the value is q, then exit the program | |
//Otherwise, try to retrieve the value from the cache | |
//If the value is in the cache, display it | |
//Otherwise, display a message that the value is not in the cache | |
string? input = Console.ReadLine(); | |
if (input == "q") | |
{ | |
break; | |
} | |
else | |
{ | |
if (cache.TryGetValue(input, out string value)) | |
{ | |
Console.WriteLine($"The value of '{input}' is in the cache."); | |
} | |
else | |
{ | |
Console.WriteLine($"The value of '{input}' is not in the cache."); | |
cache.Set(key: input, value: input); | |
} | |
} | |
} |
Let's have a look at the values we get back through the dotnet-counters
tool:
Remark: If you want to learn on how to use the dotnet-counters tool, you can have a look at one of my earlier posts.