[Exporter.Prometheus] Reduce allocations#7465
Conversation
Add missing `in` modifiers on methods accepting parameters of type `PrometheusProtocol`.
Add support for the `dots` and `values` escaping schemes. Contributes to open-telemetry#7246.
Fix incorrectly sorted `[InlineData]` attributes.
- Update comments. - Add `Debug.Assert()`. - Add PR number to CHANGELOGs.
- Add missing coverage. - Remove unused code.
Add additional interesting test cases taken from https://github.com/prometheus/common/blob/43de10cc658055c6b5e4d619edc917d5af493409/model/metric_test.go.
Add support for the `allow-utf-8` escaping scheme. Fixes open-telemetry#7246.
Add PR number.
Add missing test coverage.
Add a new benchmark that compares the different text formats and escaping schemes.
Avoid allocations when a label does not need to be sanitized.
Make lines less long.
Further avoid allocations when writing labels.
Move entries to the right place.
Update comments.
Reduce allocations by caching scope labels and their data for each metric.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7465 +/- ##
==========================================
+ Coverage 90.18% 90.28% +0.09%
==========================================
Files 287 281 -6
Lines 15553 15031 -522
==========================================
- Hits 14027 13571 -456
+ Misses 1526 1460 -66
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Add missing `this`.
- Handle label vs. metric name escaping correctly. - Fix colliding exemplars with different escapings. - Fix scope labels not being escaped.
Fix typo in comment.
Fix method ordering after merge.
|
Just need to re-run the benchmarks after merging latest changes. |
Allocations (deterministic — the metric this change targets)
A consistent ~3.2 KB drop per call across every format and escaping scheme — confirming the caching removes the per-call scope-label collection rebuilds regardless of the negotiated escaping, exactly as intended. |
There was a problem hiding this comment.
Pull request overview
This PR reduces Prometheus text serialization allocations by caching the computed otel_scope_* label collections per Metric, so they can be reused across repeated WriteMetric calls rather than rebuilt every time.
Changes:
- Added
ConditionalWeakTablecaches for scope labelLabelDataand the merged scope label key/value pairs, keyed byMetric. - Switched serialization paths to use cached scope labels via
GetScopeLabelData/GetScopeLabels. - Introduced small helper methods to encapsulate cache access while keeping existing scope-label construction logic intact.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private static readonly ConditionalWeakTable<Metric, List<KeyValuePair<string, string>>> ScopeLabelsCache = []; | ||
| private static readonly ConditionalWeakTable<Metric, List<LabelData>> ScopeLabelDataCache = []; | ||
| #else | ||
| private static readonly ConditionalWeakTable<Metric, List<KeyValuePair<string, string>>> ScopeLabelsCache = new(); |
There was a problem hiding this comment.
Note for myself: [] is not working on .NET Fx for this case.
Changes
Reduce allocations by caching scope labels and their data for each metric.
Discovered while benchmarking v1.15.2 against v1.16.0.
Builds on top of #7440.
See #7465 (comment) for benchmark results
Old benchmark results
Indicative improvements:
Prometheus serializer: allocated bytes per call (net10.0, Default job)
Prometheus serializer: time per call in ns (net10.0, Default job, @1000 scale)
Merge requirement checklist
Unit tests added/updatedAppropriateCHANGELOG.mdfiles updated for non-trivial changesChanges in public API reviewed (if applicable)