@@ -10,6 +10,7 @@ import (
1010 "context"
1111 "encoding/json"
1212 "fmt"
13+ "hash/fnv"
1314 "io"
1415 "math"
1516 "os"
@@ -190,6 +191,10 @@ type MetricsRecorder struct {
190191 // round-trip) that requires a mutex to be safe for concurrent usage. We
191192 // therefore give it its own mutex to avoid blocking other methods.
192193 writeSummaryMu syncutil.Mutex
194+
195+ // childMetricNameCache caches the encoded names for child metrics to avoid
196+ // rebuilding them on every recording. Uses syncutil.Map for lock-free reads.
197+ childMetricNameCache syncutil.Map [uint64 , string ]
193198}
194199
195200// NewMetricsRecorder initializes a new MetricsRecorder object that uses the
@@ -468,7 +473,7 @@ func (mr *MetricsRecorder) GetTimeSeriesData(childMetrics bool) []tspb.TimeSerie
468473 source : mr .mu .desc .NodeID .String (),
469474 timestampNanos : now .UnixNano (),
470475 }
471- recorder .recordChangefeedChildMetrics (& data )
476+ recorder .recordChangefeedChildMetrics (& data , & mr . childMetricNameCache )
472477
473478 // Record child metrics from app-level registries for secondary tenants
474479 for tenantID , r := range mr .mu .tenantRegistries {
@@ -478,7 +483,7 @@ func (mr *MetricsRecorder) GetTimeSeriesData(childMetrics bool) []tspb.TimeSerie
478483 source : tsutil .MakeTenantSource (mr .mu .desc .NodeID .String (), tenantID .String ()),
479484 timestampNanos : now .UnixNano (),
480485 }
481- tenantRecorder .recordChangefeedChildMetrics (& data )
486+ tenantRecorder .recordChangefeedChildMetrics (& data , & mr . childMetricNameCache )
482487 }
483488
484489 atomic .CompareAndSwapInt64 (& mr .lastDataCount , lastDataCount , int64 (len (data )))
@@ -949,10 +954,40 @@ func (rr registryRecorder) recordChild(
949954 })
950955}
951956
957+ func hashLabels (labels []* prometheusgo.LabelPair ) uint64 {
958+ h := fnv .New64a ()
959+ for _ , label := range labels {
960+ h .Write ([]byte (label .GetName ()))
961+ h .Write ([]byte (label .GetValue ()))
962+ }
963+ return h .Sum64 ()
964+ }
965+
966+ // getOrComputeMetricName looks up the encoded metric name in the cache,
967+ // or computes it using the provided computeFn if not found.
968+ func getOrComputeMetricName (
969+ cache * syncutil.Map [uint64 , string ],
970+ labels []* prometheusgo.LabelPair ,
971+ computeFn func () string ,
972+ ) string {
973+ if cache == nil {
974+ return computeFn ()
975+ }
976+ labelHash := hashLabels (labels )
977+ if cached , ok := cache .Load (labelHash ); ok {
978+ return * cached
979+ }
980+ name := computeFn ()
981+ cache .Store (labelHash , & name )
982+ return name
983+ }
984+
952985// recordChangefeedChildMetrics iterates through changefeed metrics in the registry and processes child metrics
953986// for those that have TsdbRecordLabeled set to true in their metadata.
954987// Records up to 1024 child metrics per metric to prevent unbounded memory usage and performance issues.
955- func (rr registryRecorder ) recordChangefeedChildMetrics (dest * []tspb.TimeSeriesData ) {
988+ func (rr registryRecorder ) recordChangefeedChildMetrics (
989+ dest * []tspb.TimeSeriesData , cache * syncutil.Map [uint64 , string ],
990+ ) {
956991 maxChildMetricsPerMetric := 1024
957992
958993 labels := rr .registry .GetLabels ()
@@ -1020,8 +1055,10 @@ func (rr registryRecorder) recordChangefeedChildMetrics(dest *[]tspb.TimeSeriesD
10201055 return
10211056 }
10221057
1023- baseName := metadata .Name + metric .EncodeLabeledName (& prometheusgo.Metric {Label : childLabels })
1024-
1058+ // Check cache for encoded name
1059+ baseName := getOrComputeMetricName (cache , childLabels , func () string {
1060+ return metadata .Name + metric .EncodeLabeledName (& prometheusgo.Metric {Label : childLabels })
1061+ })
10251062 // Record all histogram computed metrics using child-specific snapshots
10261063 for _ , c := range metric .HistogramMetricComputers {
10271064 var value float64
@@ -1063,7 +1100,13 @@ func (rr registryRecorder) recordChangefeedChildMetrics(dest *[]tspb.TimeSeriesD
10631100 } else {
10641101 return
10651102 }
1066- recordMetric (prom .GetName (false /* useStaticLabels */ )+ metric .EncodeLabeledName (childMetric ), value )
1103+
1104+ // Check cache for encoded name
1105+ metricName := getOrComputeMetricName (cache , childMetric .Label , func () string {
1106+ return prom .GetName (false /* useStaticLabels */ ) + metric .EncodeLabeledName (childMetric )
1107+ })
1108+
1109+ recordMetric (metricName , value )
10671110 childMetricsCount ++
10681111 }
10691112 promIter .Each (m .Label , processChildMetric )
0 commit comments