@@ -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,10 +27,31 @@ import (
25
27
// provided in this package.
26
28
type MetricVec struct {
27
29
mtx sync.RWMutex // Protects the children.
28
- children map [uint64 ]Metric
30
+ children map [uint64 ][] metricWithLabelValues
29
31
desc * Desc
30
32
31
- newMetric func (labelValues ... string ) Metric
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
36
+ }
37
+
38
+ // newMetricVec returns an initialized MetricVec. The concrete value is
39
+ // returned for embedding into another struct.
40
+ func newMetricVec (desc * Desc , newMetric func (lvs ... string ) Metric ) MetricVec {
41
+ return MetricVec {
42
+ children : map [uint64 ][]metricWithLabelValues {},
43
+ desc : desc ,
44
+ newMetric : newMetric ,
45
+ hashAdd : hashAdd ,
46
+ hashAddByte : hashAddByte ,
47
+ }
48
+ }
49
+
50
+ // metricWithLabelValues provides the metric and its label values for
51
+ // disambiguation on hash collision.
52
+ type metricWithLabelValues struct {
53
+ values []string
54
+ metric Metric
32
55
}
33
56
34
57
// Describe implements Collector. The length of the returned slice
@@ -42,8 +65,10 @@ func (m *MetricVec) Collect(ch chan<- Metric) {
42
65
m .mtx .RLock ()
43
66
defer m .mtx .RUnlock ()
44
67
45
- for _ , metric := range m .children {
46
- ch <- metric
68
+ for _ , metrics := range m .children {
69
+ for _ , metric := range metrics {
70
+ ch <- metric .metric
71
+ }
47
72
}
48
73
}
49
74
@@ -77,16 +102,7 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
77
102
return nil , err
78
103
}
79
104
80
- m .mtx .RLock ()
81
- metric , ok := m .children [h ]
82
- m .mtx .RUnlock ()
83
- if ok {
84
- return metric , nil
85
- }
86
-
87
- m .mtx .Lock ()
88
- defer m .mtx .Unlock ()
89
- return m .getOrCreateMetric (h , lvs ... ), nil
105
+ return m .getOrCreateMetric (h , lvs ), nil
90
106
}
91
107
92
108
// GetMetricWith returns the Metric for the given Labels map (the label names
@@ -107,20 +123,7 @@ func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
107
123
return nil , err
108
124
}
109
125
110
- m .mtx .RLock ()
111
- metric , ok := m .children [h ]
112
- m .mtx .RUnlock ()
113
- if ok {
114
- return metric , nil
115
- }
116
-
117
- lvs := make ([]string , len (labels ))
118
- for i , label := range m .desc .variableLabels {
119
- lvs [i ] = labels [label ]
120
- }
121
- m .mtx .Lock ()
122
- defer m .mtx .Unlock ()
123
- return m .getOrCreateMetric (h , lvs ... ), nil
126
+ return m .getOrCreateMetric (h , labels ), nil
124
127
}
125
128
126
129
// WithLabelValues works as GetMetricWithLabelValues, but panics if an error
@@ -168,11 +171,7 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
168
171
if err != nil {
169
172
return false
170
173
}
171
- if _ , ok := m .children [h ]; ! ok {
172
- return false
173
- }
174
- delete (m .children , h )
175
- return true
174
+ return m .deleteByHash (h , lvs )
176
175
}
177
176
178
177
// Delete deletes the metric where the variable labels are the same as those
@@ -193,10 +192,31 @@ func (m *MetricVec) Delete(labels Labels) bool {
193
192
if err != nil {
194
193
return false
195
194
}
196
- if _ , ok := m .children [h ]; ! ok {
195
+
196
+ return m .deleteByHash (h , labels )
197
+ }
198
+
199
+ // deleteByHash removes the metric from the hash bucket h. If there are
200
+ // multiple matches in the bucket, use lvs to select a metric and remove only
201
+ // that metric.
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 {
205
+ metrics , ok := m .children [h ]
206
+ if ! ok {
197
207
return false
198
208
}
199
- delete (m .children , h )
209
+
210
+ i := m .findMetric (metrics , values )
211
+ if i >= len (metrics ) {
212
+ return false
213
+ }
214
+
215
+ if len (metrics ) > 1 {
216
+ m .children [h ] = append (metrics [:i ], metrics [i + 1 :]... )
217
+ } else {
218
+ delete (m .children , h )
219
+ }
200
220
return true
201
221
}
202
222
@@ -216,7 +236,8 @@ func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
216
236
}
217
237
h := hashNew ()
218
238
for _ , val := range vals {
219
- h = hashAdd (h , val )
239
+ h = m .hashAdd (h , val )
240
+ h = m .hashAddByte (h , model .SeparatorByte )
220
241
}
221
242
return h , nil
222
243
}
@@ -231,19 +252,125 @@ func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
231
252
if ! ok {
232
253
return 0 , fmt .Errorf ("label name %q missing in label map" , label )
233
254
}
234
- h = hashAdd (h , val )
255
+ h = m .hashAdd (h , val )
256
+ h = m .hashAddByte (h , model .SeparatorByte )
235
257
}
236
258
return h , nil
237
259
}
238
260
239
- func (m * MetricVec ) getOrCreateMetric (hash uint64 , labelValues ... string ) Metric {
240
- metric , ok := m .children [hash ]
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 )
241
278
if ! ok {
242
- // Copy labelValues. Otherwise, they would be allocated even if we don't go
243
- // down this code path.
244
- copiedLabelValues := append (make ([]string , 0 , len (labelValues )), labelValues ... )
245
- metric = m .newMetric (copiedLabelValues ... )
246
- m .children [hash ] = metric
279
+ lvs := m .copyLabelValues (lvs )
280
+ metric = m .newMetric (lvs ... )
281
+ m .children [hash ] = append (m .children [hash ], metricWithLabelValues {values : lvs , metric : metric })
247
282
}
248
283
return metric
249
284
}
285
+
286
+ // getMetric while handling possible collisions in the hash space. Must be
287
+ // called while holding read mutex.
288
+ //
289
+ // lvs must be of type Labels or []string.
290
+ func (m * MetricVec ) getMetric (h uint64 , lvs interface {}) (Metric , bool ) {
291
+ metrics , ok := m .children [h ]
292
+ if ok {
293
+ return m .selectMetric (metrics , lvs )
294
+ }
295
+
296
+ return nil , false
297
+ }
298
+
299
+ func (m * MetricVec ) selectMetric (metrics []metricWithLabelValues , lvs interface {}) (Metric , bool ) {
300
+ i := m .findMetric (metrics , lvs )
301
+
302
+ if i < len (metrics ) {
303
+ return metrics [i ].metric , true
304
+ }
305
+
306
+ return nil , false
307
+ }
308
+
309
+ // findMetric returns the index of the matching metric or len(metrics) if not
310
+ // found.
311
+ func (m * MetricVec ) findMetric (metrics []metricWithLabelValues , lvs interface {}) int {
312
+ for i , metric := range metrics {
313
+ if m .matchLabels (metric .values , lvs ) {
314
+ return i
315
+ }
316
+ }
317
+
318
+ return len (metrics )
319
+ }
320
+
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
343
+ }
344
+ }
345
+
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" )
352
+ }
353
+ }
354
+
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
376
+ }
0 commit comments