@@ -60,6 +60,19 @@ internal abstract class TextFormatSerializer
6060 private static readonly long UnixEpochTicks = new DateTimeOffset ( 1970 , 1 , 1 , 0 , 0 , 0 , TimeSpan . Zero ) . Ticks ;
6161#endif
6262
63+ // Scope labels (otel_scope_*) depend only on the Metric's invariant
64+ // scope (meter name/version/schema-url/tags) and are not affected by
65+ // the negotiated escaping scheme, so the built label collections can
66+ // be computed once per Metric and reused across every serialization
67+ // call instead of being rebuilt for each call to WriteMetric.
68+ #if NET
69+ private static readonly ConditionalWeakTable < Metric , List < KeyValuePair < string , string > > > ScopeLabelsCache = [ ] ;
70+ private static readonly ConditionalWeakTable < Metric , List < LabelData > > ScopeLabelDataCache = [ ] ;
71+ #else
72+ private static readonly ConditionalWeakTable < Metric , List < KeyValuePair < string , string > > > ScopeLabelsCache = new ( ) ;
73+ private static readonly ConditionalWeakTable < Metric , List < LabelData > > ScopeLabelDataCache = new ( ) ;
74+ #endif
75+
6376 private static readonly string [ ] ReservedExemplarLabelNames = [ "trace_id" , "span_id" ] ;
6477 private static readonly double [ ] ExactPowersOfTen =
6578 [
@@ -341,7 +354,7 @@ internal static int GetNextSerializedTagsBufferSize(int currentBufferSize)
341354 // the scrape immediately rather than repeatedly re-entering this allocation.
342355 var newBufferSize = currentBufferSize * 2 ;
343356
344- return newBufferSize <= 0 || newBufferSize > MaxSerializedTagsBufferSize
357+ return newBufferSize is <= 0 or > MaxSerializedTagsBufferSize
345358 ? throw new InvalidOperationException ( "The serialized Prometheus tag set exceeded the maximum supported size." )
346359 : newBufferSize ;
347360 }
@@ -813,7 +826,7 @@ internal int WriteTags(
813826
814827 if ( ! options . SuppressScopeInfo )
815828 {
816- foreach ( var scopeLabel in CreateScopeLabelData ( metric ) )
829+ foreach ( var scopeLabel in GetScopeLabelData ( metric ) )
817830 {
818831 // Scope labels (otel_scope_*) are already in their target Prometheus form, so they
819832 // are written verbatim and not re-escaped by the negotiated scheme, exactly as the
@@ -837,7 +850,7 @@ void WriteScopeLabels()
837850 // tags they can never collide with an already-written label. They only need to be
838851 // written (which also records their output keys so point tags can detect a collision
839852 // with a scope label).
840- foreach ( var scopeLabel in CreateScopeLabels ( metric ) )
853+ foreach ( var scopeLabel in GetScopeLabels ( metric ) )
841854 {
842855 _ = TryWriteLabel ( scopeLabel . Key , scopeLabel . Value , isScopeLabel : true ) ;
843856 }
@@ -1290,12 +1303,15 @@ private static string NormalizeLabelKey(string value)
12901303 return builder . ToString ( ) ;
12911304 }
12921305
1306+ private static List < KeyValuePair < string , string > > GetScopeLabels ( Metric metric )
1307+ => ScopeLabelsCache . GetValue ( metric , CreateScopeLabels ) ;
1308+
12931309 private static List < KeyValuePair < string , string > > CreateScopeLabels ( Metric metric )
12941310 {
12951311 var orderedKeys = new List < string > ( ) ;
12961312 var labelsByOutputKey = new Dictionary < string , List < LabelData > > ( StringComparer . Ordinal ) ;
12971313
1298- foreach ( var label in CreateScopeLabelData ( metric ) )
1314+ foreach ( var label in GetScopeLabelData ( metric ) )
12991315 {
13001316 if ( ! labelsByOutputKey . TryGetValue ( label . OutputKey , out var bucket ) )
13011317 {
@@ -1317,6 +1333,9 @@ private static List<KeyValuePair<string, string>> CreateScopeLabels(Metric metri
13171333 return scopeLabels ;
13181334 }
13191335
1336+ private static List < LabelData > GetScopeLabelData ( Metric metric )
1337+ => ScopeLabelDataCache . GetValue ( metric , CreateScopeLabelData ) ;
1338+
13201339 private static List < LabelData > CreateScopeLabelData ( Metric metric )
13211340 {
13221341 var scopeLabels = new List < LabelData > ( 3 )
@@ -2105,7 +2124,7 @@ private int WriteOutputLabelKey(byte[] buffer, int cursor, string key)
21052124 EscapingScheme . AllowUtf8 => WriteLabelName ( buffer , cursor , key ) ,
21062125
21072126 // The dots and values schemes escape the name and quote it only if a colon survives.
2108- _ => WriteEscapedLabelKey ( buffer , cursor , key , this . Escaping ) ,
2127+ EscapingScheme . Dots or EscapingScheme . Values or _ => WriteEscapedLabelKey ( buffer , cursor , key , this . Escaping ) ,
21092128 } ;
21102129 }
21112130
0 commit comments