Add built-in OpenTelemetry metrics to MemoryCache#126146
Add built-in OpenTelemetry metrics to MemoryCache#126146rjmurillo wants to merge 1 commit intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @dotnet/area-extensions-caching |
There was a problem hiding this comment.
Pull request overview
Adds built-in OpenTelemetry metrics support to Microsoft.Extensions.Caching.Memory.MemoryCache by exposing additional cache statistics and wiring them into observable instruments, gated by MemoryCacheOptions.TrackStatistics.
Changes:
- Added
TotalEvictionstoMemoryCacheStatisticsand tracked eviction counts inMemoryCache. - Introduced
MemoryCacheOptions.Nameand newMemoryCachector overload supportingIMeterFactory. - Added unit tests for metrics publication and eviction statistics, plus conditional DiagnosticSource references for non-inbox TFMs.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs | Implements eviction counting and publishes OTEL observable instruments via Meter/IMeterFactory. |
| src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCacheOptions.cs | Adds Name option used as a metrics dimension. |
| src/libraries/Microsoft.Extensions.Caching.Memory/src/Microsoft.Extensions.Caching.Memory.csproj | Adds conditional DiagnosticSource reference for TFMs missing in-box metrics types. |
| src/libraries/Microsoft.Extensions.Caching.Memory/ref/Microsoft.Extensions.Caching.Memory.cs | Updates public API surface for new ctor overload and Name option. |
| src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheMetricsTests.cs | Adds tests validating instrument creation and basic measurements. |
| src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheGetCurrentStatisticsTests.cs | Adds tests for eviction statistics behavior. |
| src/libraries/Microsoft.Extensions.Caching.Memory/tests/Microsoft.Extensions.Caching.Memory.Tests.csproj | Adds conditional DiagnosticSource project reference for .NETFramework tests. |
| src/libraries/Microsoft.Extensions.Caching.Abstractions/src/MemoryCacheStatistics.cs | Adds TotalEvictions to the statistics snapshot type. |
| src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.cs | Updates ref assembly for TotalEvictions. |
src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Outdated
Show resolved
Hide resolved
2d7d40d to
f3ef1c4
Compare
cincuranet
left a comment
There was a problem hiding this comment.
For the _accumulatedEvictions comments from Copilot, I think the idea with bool return is sound.
Or maybe putting the logic into RemoveEntry - given there's already _cacheSize handling - with a flag whether to count or not (not all RemoveEntry calls (should) update _accumulatedEvictions).
Up to you.
src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Outdated
Show resolved
Hide resolved
f3ef1c4 to
98aa196
Compare
src/libraries/Microsoft.Extensions.Caching.Abstractions/src/MemoryCacheStatistics.cs
Show resolved
Hide resolved
Implements dotnet#124140. Adds observable OTEL instruments for cache requests (hit/miss), evictions, entry count, and estimated size. Key design decisions: - MeterOptions with cache.name tag for per-cache meter deduplication - WeakReference<MemoryCache> in observable callbacks to prevent GC leaks - RemoveEntry returns bool for accurate eviction counting - IEnumerable<Measurement<long>> overloads to avoid phantom zero measurements - No instruments without IMeterFactory (GetCurrentStatistics still works) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c4b258a to
eace3f4
Compare
|
|
||
| _meter = meterFactory?.Create(new MeterOptions("Microsoft.Extensions.Caching.Memory.MemoryCache") | ||
| { | ||
| Tags = [new("cache.name", _options.Name)] |
There was a problem hiding this comment.
InitializeMetrics creates 4 new observable instruments per MemoryCache instance. When an IMeterFactory is used, DefaultMeterFactory caches and returns the same Meter for the same name+tags; when no factory is provided, SharedMeter.Instance is a singleton. In both cases, creating multiple MemoryCache instances with the same options.Name will repeatedly add duplicate observable instruments to the same Meter with no way to unregister them, leading to unbounded instrument growth and potentially duplicated/incorrect metric streams. Consider ensuring instruments are created once per meter (e.g., static/once-init per meter) and have the observable callbacks aggregate across all caches sharing that meter/name (using a registry of weak references), rather than creating per-cache instruments.
| Tags = [new("cache.name", _options.Name)] | |
| Tags = | |
| [ | |
| new("cache.name", _options.Name), | |
| new("cache.instance.id", RuntimeHelpers.GetHashCode(this)) | |
| ] |
There was a problem hiding this comment.
Observable instruments on a shared Meter are not deduplicated. Each callback holds a WeakReference<MemoryCache>. GC collects a cache, the callback returns empty measurements. Cost is near zero.
Adding cache.instance.id breaks meter-level deduplication in DefaultMeterFactory. Different tags produce different meters. That defeats MeterOptions.Tags.
This follows the System.Net.Http.MetricsHandler.SharedMeter pattern from PR #87319. One to ten cache instances — instrument overhead is negligible.
@cincuranet confirmed the approach.
Summary
Adds OpenTelemetry metrics to
MemoryCache. Follows theSystem.Net.Httppattern (#87319).Fixes #124140
API Changes
Microsoft.Extensions.Caching.Abstractions
Microsoft.Extensions.Caching.Memory
Instruments
Meter:
Microsoft.Extensions.Caching.Memory.MemoryCachecache.requestscache.name,cache.request.type(hit/miss)cache.evictionscache.namecache.entriescache.namecache.estimated_sizecache.nameDesign
TrackStatistics— no instruments created unlessTrackStatistics = trueIMeterFactory, no problem. SingletonSharedMeterwith NOPDispose. Same pattern asSystem.Net.Http.MetricsHandler.SharedMeterTags = [new("cache.name", _options.Name)]for meter deduplication inDefaultMeterFactoryWeakReference<MemoryCache>. Dead caches return empty measurements. No GC leaksFunc<IEnumerable<Measurement<long>>>prevents phantom zero measurements from disposed or collected cachesRemoveEntrycallsConcurrentDictionary.TryRemove(KVP). Only the thread that removes the entry incrementsTotalEvictions. No double-countingMemoryCacheServiceCollectionExtensionsTests