@@ -60,7 +60,7 @@ func (c *CachedTGatherer) Gather() (_ []*dto.MetricFamily, done func(), err erro
6060 c .mMu .RLock ()
6161
6262 // BenchmarkCachedTGatherer_Update shows, even for 1 million metrics with 1000 families
63- // this is efficient enough (~250µs 50 kB per op), no need to cache it for now.
63+ // this is efficient enough (~300µs and ~ 50 kB per op), no need to cache it for now.
6464 return internal .NormalizeMetricFamilies (c .metricFamilyByName ), c .mMu .RUnlock , nil
6565}
6666
@@ -114,6 +114,11 @@ type Insert struct {
114114// Update goes through inserts and deletions and updates current cache in concurrency safe manner.
115115// If reset is set to true, all inserts and deletions are working on empty cache. In such case
116116// this implementation tries to reuse memory from existing cached item when possible.
117+ //
118+ // Update reuses insert struct memory, so after use, Insert slice and its elements cannot be reused
119+ // outside of this method.
120+ // TODO(bwplotka): Lack of copying can pose memory safety problems if insert variables are reused. Consider copying if value
121+ // is different. Yet it gives significant allocation gains.
117122func (c * CachedTGatherer ) Update (reset bool , inserts []Insert , deletions []Key ) error {
118123 c .mMu .Lock ()
119124 defer c .mMu .Unlock ()
@@ -126,48 +131,48 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
126131 }
127132
128133 errs := prometheus.MultiError {}
129- for _ , ins := range inserts {
134+ for i := range inserts {
130135 // TODO(bwplotka): Validate more about this insert?
131- if err := ins .isValid (); err != nil {
136+ if err := inserts [ i ] .isValid (); err != nil {
132137 errs .Append (err )
133138 continue
134139 }
135140
136141 // Update metric family.
137- mf , ok := c .metricFamilyByName [ins .FQName ]
142+ mf , ok := c .metricFamilyByName [inserts [ i ] .FQName ]
138143 if ! ok {
139144 mf = & dto.MetricFamily {}
140- mf .Name = proto . String ( ins . FQName )
145+ mf .Name = & inserts [ i ]. FQName
141146 } else if reset {
142147 // Reset metric slice, since we want to start from scratch.
143148 mf .Metric = mf .Metric [:0 ]
144149 }
145- mf .Type = ins .ValueType .ToDTO ()
146- mf .Help = proto . String ( ins . Help )
150+ mf .Type = inserts [ i ] .ValueType .ToDTO ()
151+ mf .Help = & inserts [ i ]. Help
147152
148- currMetricFamilies [ins .FQName ] = mf
153+ currMetricFamilies [inserts [ i ] .FQName ] = mf
149154
150155 // Update metric pointer.
151- hSum := ins .hash ()
156+ hSum := inserts [ i ] .hash ()
152157 m , ok := c .metrics [hSum ]
153158 if ! ok {
154- m = & dto.Metric {Label : make ([]* dto.LabelPair , 0 , len (ins .LabelNames ))}
155- for i := range ins .LabelNames {
159+ m = & dto.Metric {Label : make ([]* dto.LabelPair , 0 , len (inserts [ i ] .LabelNames ))}
160+ for j := range inserts [ i ] .LabelNames {
156161 m .Label = append (m .Label , & dto.LabelPair {
157- Name : proto . String ( ins . LabelNames [i ]) ,
158- Value : proto . String ( ins . LabelValues [i ]) ,
162+ Name : & inserts [ i ]. LabelNames [j ] ,
163+ Value : & inserts [ i ]. LabelValues [j ] ,
159164 })
160165 }
161- sort .Sort (labelPairSorter (m .Label ))
166+ sort .Sort (internal . LabelPairSorter (m .Label ))
162167 }
163168
164- switch ins .ValueType {
169+ switch inserts [ i ] .ValueType {
165170 case prometheus .CounterValue :
166171 v := m .Counter
167172 if v == nil {
168173 v = & dto.Counter {}
169174 }
170- v .Value = proto . Float64 ( ins . Value )
175+ v .Value = & inserts [ i ]. Value
171176 m .Counter = v
172177 m .Gauge = nil
173178 m .Untyped = nil
@@ -176,7 +181,7 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
176181 if v == nil {
177182 v = & dto.Gauge {}
178183 }
179- v .Value = proto . Float64 ( ins . Value )
184+ v .Value = & inserts [ i ]. Value
180185 m .Counter = nil
181186 m .Gauge = v
182187 m .Untyped = nil
@@ -185,17 +190,17 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
185190 if v == nil {
186191 v = & dto.Untyped {}
187192 }
188- v .Value = proto . Float64 ( ins . Value )
193+ v .Value = & inserts [ i ]. Value
189194 m .Counter = nil
190195 m .Gauge = nil
191196 m .Untyped = v
192197 default :
193- return fmt .Errorf ("unsupported value type %v" , ins .ValueType )
198+ return fmt .Errorf ("unsupported value type %v" , inserts [ i ] .ValueType )
194199 }
195200
196201 m .TimestampMs = nil
197- if ins .Timestamp != nil {
198- m .TimestampMs = proto .Int64 (ins .Timestamp .Unix ()* 1000 + int64 (ins .Timestamp .Nanosecond ()/ 1000000 ))
202+ if inserts [ i ] .Timestamp != nil {
203+ m .TimestampMs = proto .Int64 (inserts [ i ] .Timestamp .Unix ()* 1000 + int64 (inserts [ i ] .Timestamp .Nanosecond ()/ 1000000 ))
199204 }
200205 currMetrics [hSum ] = m
201206
@@ -254,19 +259,3 @@ func (c *CachedTGatherer) Update(reset bool, inserts []Insert, deletions []Key)
254259 c .metricFamilyByName = currMetricFamilies
255260 return errs .MaybeUnwrap ()
256261}
257-
258- // labelPairSorter implements sort.Interface. It is used to sort a slice of
259- // dto.LabelPair pointers.
260- type labelPairSorter []* dto.LabelPair
261-
262- func (s labelPairSorter ) Len () int {
263- return len (s )
264- }
265-
266- func (s labelPairSorter ) Swap (i , j int ) {
267- s [i ], s [j ] = s [j ], s [i ]
268- }
269-
270- func (s labelPairSorter ) Less (i , j int ) bool {
271- return s [i ].GetName () < s [j ].GetName ()
272- }
0 commit comments