@@ -16,6 +16,8 @@ package prometheus
16
16
import (
17
17
"fmt"
18
18
"sync"
19
+
20
+ "github.com/prometheus/common/model"
19
21
)
20
22
21
23
// MetricVec is a Collector to bundle metrics of the same name that
@@ -25,25 +27,29 @@ import (
25
27
// provided in this package.
26
28
type MetricVec struct {
27
29
mtx sync.RWMutex // Protects the children.
28
- children map [uint64 ][]metricLabelValue
30
+ children map [uint64 ][]metricWithLabelValues
29
31
desc * Desc
30
32
31
- newMetric func (labelValues ... string ) Metric
32
- hashAdd func (h uint64 , s string ) uint64 // replace hash function for testing collision handling
33
+ newMetric func (labelValues ... string ) Metric
34
+ hashAdd func (h uint64 , s string ) uint64 // replace hash function for testing collision handling
35
+ hashAddByte func (h uint64 , b byte ) uint64
33
36
}
34
37
35
38
// newMetricVec returns an initialized MetricVec. The concrete value is
36
39
// returned for embedding into another struct.
37
40
func newMetricVec (desc * Desc , newMetric func (lvs ... string ) Metric ) MetricVec {
38
41
return MetricVec {
39
- children : map [uint64 ][]metricLabelValue {},
40
- desc : desc ,
41
- newMetric : newMetric ,
42
- hashAdd : hashAdd ,
42
+ children : map [uint64 ][]metricWithLabelValues {},
43
+ desc : desc ,
44
+ newMetric : newMetric ,
45
+ hashAdd : hashAdd ,
46
+ hashAddByte : hashAddByte ,
43
47
}
44
48
}
45
49
46
- type metricLabelValue struct {
50
+ // metricWithLabelValues provides the metric and its label values for
51
+ // disambiguation on hash collision.
52
+ type metricWithLabelValues struct {
47
53
values []string
48
54
metric Metric
49
55
}
@@ -96,16 +102,7 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
96
102
return nil , err
97
103
}
98
104
99
- m .mtx .RLock ()
100
- metric , ok := m .getMetric (h , lvs ... )
101
- m .mtx .RUnlock ()
102
- if ok {
103
- return metric , nil
104
- }
105
-
106
- m .mtx .Lock ()
107
- defer m .mtx .Unlock ()
108
- return m .getOrCreateMetric (h , lvs ... ), nil
105
+ return m .getOrCreateMetric (h , lvs ), nil
109
106
}
110
107
111
108
// GetMetricWith returns the Metric for the given Labels map (the label names
@@ -126,21 +123,7 @@ func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
126
123
return nil , err
127
124
}
128
125
129
- m .mtx .RLock ()
130
- metrics , ok := m .children [h ]
131
- if ok && len (metrics ) == 1 {
132
- m .mtx .RUnlock ()
133
- return metrics [0 ].metric , nil
134
- }
135
- m .mtx .RUnlock ()
136
-
137
- lvs := make ([]string , len (labels ))
138
- for i , label := range m .desc .variableLabels {
139
- lvs [i ] = labels [label ]
140
- }
141
- m .mtx .Lock ()
142
- defer m .mtx .Unlock ()
143
- return m .getOrCreateMetric (h , lvs ... ), nil
126
+ return m .getOrCreateMetric (h , labels ), nil
144
127
}
145
128
146
129
// WithLabelValues works as GetMetricWithLabelValues, but panics if an error
@@ -188,7 +171,7 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
188
171
if err != nil {
189
172
return false
190
173
}
191
- return m .deleteByHash (h , lvs ... )
174
+ return m .deleteByHash (h , lvs )
192
175
}
193
176
194
177
// Delete deletes the metric where the variable labels are the same as those
@@ -210,28 +193,21 @@ func (m *MetricVec) Delete(labels Labels) bool {
210
193
return false
211
194
}
212
195
213
- var lvs []string
214
- for _ , k := range m .desc .variableLabels {
215
- lvs = append (lvs , labels [k ])
216
- }
217
-
218
- return m .deleteByHash (h , lvs ... )
196
+ return m .deleteByHash (h , labels )
219
197
}
220
198
221
199
// deleteByHash removes the metric from the hash bucket h. If there are
222
200
// multiple matches in the bucket, use lvs to select a metric and remove only
223
201
// that metric.
224
- func (m * MetricVec ) deleteByHash (h uint64 , lvs ... string ) bool {
202
+ //
203
+ // lvs MUST be of type Labels or []string or this method will panic.
204
+ func (m * MetricVec ) deleteByHash (h uint64 , values interface {}) bool {
225
205
metrics , ok := m .children [h ]
226
206
if ! ok {
227
207
return false
228
208
}
229
209
230
- if len (metrics ) < 2 {
231
- delete (m .children , h )
232
- }
233
-
234
- i := findMetric (lvs , metrics )
210
+ i := m .findMetric (metrics , values )
235
211
if i >= len (metrics ) {
236
212
return false
237
213
}
@@ -261,6 +237,7 @@ func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
261
237
h := hashNew ()
262
238
for _ , val := range vals {
263
239
h = m .hashAdd (h , val )
240
+ h = m .hashAddByte (h , model .SeparatorByte )
264
241
}
265
242
return h , nil
266
243
}
@@ -276,43 +253,51 @@ func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
276
253
return 0 , fmt .Errorf ("label name %q missing in label map" , label )
277
254
}
278
255
h = m .hashAdd (h , val )
256
+ h = m .hashAddByte (h , model .SeparatorByte )
279
257
}
280
258
return h , nil
281
259
}
282
260
283
- func (m * MetricVec ) getOrCreateMetric (hash uint64 , labelValues ... string ) Metric {
284
- metric , ok := m .getMetric (hash , labelValues ... )
261
+ // getOrCreateMetric retrieves the metric by hash and label value or creates it
262
+ // and returns the new one.
263
+ //
264
+ // lvs MUST be of type Labels or []string or this method will panic.
265
+ //
266
+ // This function holds the mutex.
267
+ func (m * MetricVec ) getOrCreateMetric (hash uint64 , lvs interface {}) Metric {
268
+ m .mtx .RLock ()
269
+ metric , ok := m .getMetric (hash , lvs )
270
+ m .mtx .RUnlock ()
271
+ if ok {
272
+ return metric
273
+ }
274
+
275
+ m .mtx .Lock ()
276
+ defer m .mtx .Unlock ()
277
+ metric , ok = m .getMetric (hash , lvs )
285
278
if ! ok {
286
- // Copy labelValues. Otherwise, they would be allocated even if we don't go
287
- // down this code path.
288
- copiedLabelValues := append (make ([]string , 0 , len (labelValues )), labelValues ... )
289
- metric = m .newMetric (copiedLabelValues ... )
290
- m .children [hash ] = append (m .children [hash ], metricLabelValue {values : copiedLabelValues , metric : metric })
279
+ lvs := m .copyLabelValues (lvs )
280
+ metric = m .newMetric (lvs ... )
281
+ m .children [hash ] = append (m .children [hash ], metricWithLabelValues {values : lvs , metric : metric })
291
282
}
292
283
return metric
293
284
}
294
285
295
286
// getMetric while handling possible collisions in the hash space. Must be
296
287
// called while holding read mutex.
297
- func (m * MetricVec ) getMetric (h uint64 , lvs ... string ) (Metric , bool ) {
288
+ //
289
+ // lvs must be of type Labels or []string.
290
+ func (m * MetricVec ) getMetric (h uint64 , lvs interface {}) (Metric , bool ) {
298
291
metrics , ok := m .children [h ]
299
292
if ok {
300
- return m .selectMetric (lvs , metrics )
293
+ return m .selectMetric (metrics , lvs )
301
294
}
302
295
303
296
return nil , false
304
297
}
305
298
306
- func (m * MetricVec ) selectMetric (lvs []string , metrics []metricLabelValue ) (Metric , bool ) {
307
- switch len (metrics ) {
308
- case 0 :
309
- return nil , false
310
- case 1 :
311
- // collisions are rare, this should be the fast path.
312
- return metrics [0 ].metric , true
313
- }
314
-
315
- i := findMetric (lvs , metrics )
299
+ func (m * MetricVec ) selectMetric (metrics []metricWithLabelValues , lvs interface {}) (Metric , bool ) {
300
+ i := m .findMetric (metrics , lvs )
316
301
317
302
if i < len (metrics ) {
318
303
return metrics [i ].metric , true
@@ -323,22 +308,69 @@ func (m *MetricVec) selectMetric(lvs []string, metrics []metricLabelValue) (Metr
323
308
324
309
// findMetric returns the index of the matching metric or len(metrics) if not
325
310
// found.
326
- func findMetric (lvs []string , metrics []metricLabelValue ) int {
327
- next:
311
+ func (m * MetricVec ) findMetric (metrics []metricWithLabelValues , lvs interface {}) int {
328
312
for i , metric := range metrics {
329
- if len (metric .values ) != len ( lvs ) {
330
- continue
313
+ if m . matchLabels (metric .values , lvs ) {
314
+ return i
331
315
}
316
+ }
317
+
318
+ return len (metrics )
319
+ }
332
320
333
- for j , v := range metric .values {
334
- if v != lvs [j ] {
335
- continue next
321
+ func (m * MetricVec ) matchLabels (values []string , lvs interface {}) bool {
322
+ switch lvs := lvs .(type ) {
323
+ case []string :
324
+ if len (values ) != len (lvs ) {
325
+ return false
326
+ }
327
+
328
+ for i , v := range values {
329
+ if v != lvs [i ] {
330
+ return false
331
+ }
332
+ }
333
+
334
+ return true
335
+ case Labels :
336
+ if len (lvs ) != len (values ) {
337
+ return false
338
+ }
339
+
340
+ for i , k := range m .desc .variableLabels {
341
+ if values [i ] != lvs [k ] {
342
+ return false
336
343
}
337
344
}
338
- // falling out of the loop here means we have a match!
339
345
340
- return i
346
+ return true
347
+ default :
348
+ // If we reach this condition, there is an unexpected type being used
349
+ // as a labels value. Either add branch here for the new type or fix
350
+ // the bug causing the type to be passed in.
351
+ panic ("unsupported type" )
341
352
}
353
+ }
342
354
343
- return len (metrics )
355
+ // copyLabelValues copies the labels values into common string slice format to
356
+ // use when allocating the metric and to keep track of hash collision
357
+ // ambiguity.
358
+ //
359
+ // lvs must be of type Labels or []string or this method will panic.
360
+ func (m * MetricVec ) copyLabelValues (lvs interface {}) []string {
361
+ var labelValues []string
362
+ switch lvs := lvs .(type ) {
363
+ case []string :
364
+ labelValues = make ([]string , len (lvs ))
365
+ copy (labelValues , lvs )
366
+ case Labels :
367
+ labelValues = make ([]string , len (lvs ))
368
+ for i , k := range m .desc .variableLabels {
369
+ labelValues [i ] = lvs [k ]
370
+ }
371
+ default :
372
+ panic (fmt .Sprintf ("unsupported type for lvs: %#v" , lvs ))
373
+ }
374
+
375
+ return labelValues
344
376
}
0 commit comments