Skip to content

Commit c5b7fcc

Browse files
authored
Merge pull request #226 from prometheus/beorn7/alloc
Bring back zero-alloc label-value access for metric vecs
2 parents a4d14b3 + 1b26087 commit c5b7fcc

File tree

9 files changed

+141
-90
lines changed

9 files changed

+141
-90
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
## 0.8.0 / 2016-08-17
2+
* [CHANGE] Registry is doing more consistency checks. This might break
3+
existing setups that used to export inconsistent metrics.
4+
* [CHANGE] Pushing to Pushgateway moved to package `push` and changed to allow
5+
arbitrary grouping.
6+
* [CHANGE] Removed `SelfCollector`.
7+
* [CHANGE] Removed `PanicOnCollectError` and `EnableCollectChecks` methods.
8+
* [CHANGE] Moved packages to the prometheus/common repo: `text`, `model`,
9+
`extraction`.
10+
* [CHANGE] Deprecated a number of functions.
11+
* [FEATURE] Allow custom registries. Added `Registerer` and `Gatherer`
12+
interfaces.
13+
* [FEATURE] Separated HTTP exposition, allowing custom HTTP handlers (package
14+
`promhttp`) and enabling the creation of other exposition mechanisms.
15+
* [FEATURE] `MustRegister` is variadic now, allowing registration of many
16+
collectors in one call.
17+
* [FEATURE] Added HTTP API v1 package.
18+
* [ENHANCEMENT] Numerous documentation improvements.
19+
* [ENHANCEMENT] Improved metric sorting.
20+
* [ENHANCEMENT] Inlined fnv64a hashing for improved performance.
21+
* [ENHANCEMENT] Several test improvements.
22+
* [BUGFIX] Handle collisions in MetricVec.
23+
124
## 0.7.0 / 2015-07-27
225
* [CHANGE] Rename ExporterLabelPrefix to ExportedLabelPrefix.
326
* [BUGFIX] Closed gaps in metric consistency check.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.7.0
1+
0.8.0

prometheus/counter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (c *counter) Add(v float64) {
8282
// CounterVec embeds MetricVec. See there for a full list of methods with
8383
// detailed documentation.
8484
type CounterVec struct {
85-
MetricVec
85+
*MetricVec
8686
}
8787

8888
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and

prometheus/gauge.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func NewGauge(opts GaugeOpts) Gauge {
5858
// (e.g. number of operations queued, partitioned by user and operation
5959
// type). Create instances with NewGaugeVec.
6060
type GaugeVec struct {
61-
MetricVec
61+
*MetricVec
6262
}
6363

6464
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and

prometheus/histogram.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ func (h *histogram) Write(out *dto.Metric) error {
287287
// (e.g. HTTP request latencies, partitioned by status code and method). Create
288288
// instances with NewHistogramVec.
289289
type HistogramVec struct {
290-
MetricVec
290+
*MetricVec
291291
}
292292

293293
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and

prometheus/summary.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ func (s quantSort) Less(i, j int) bool {
390390
// (e.g. HTTP request latencies, partitioned by status code and method). Create
391391
// instances with NewSummaryVec.
392392
type SummaryVec struct {
393-
MetricVec
393+
*MetricVec
394394
}
395395

396396
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and

prometheus/untyped.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func NewUntyped(opts UntypedOpts) Untyped {
5656
// labels. This is used if you want to count the same thing partitioned by
5757
// various dimensions. Create instances with NewUntypedVec.
5858
type UntypedVec struct {
59-
MetricVec
59+
*MetricVec
6060
}
6161

6262
// NewUntypedVec creates a new UntypedVec based on the provided UntypedOpts and

prometheus/vec.go

Lines changed: 110 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ type MetricVec struct {
3737

3838
// newMetricVec returns an initialized MetricVec. The concrete value is
3939
// returned for embedding into another struct.
40-
func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) MetricVec {
41-
return MetricVec{
40+
func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
41+
return &MetricVec{
4242
children: map[uint64][]metricWithLabelValues{},
4343
desc: desc,
4444
newMetric: newMetric,
@@ -102,7 +102,7 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
102102
return nil, err
103103
}
104104

105-
return m.getOrCreateMetric(h, lvs), nil
105+
return m.getOrCreateMetricWithLabelValues(h, lvs), nil
106106
}
107107

108108
// GetMetricWith returns the Metric for the given Labels map (the label names
@@ -123,7 +123,7 @@ func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
123123
return nil, err
124124
}
125125

126-
return m.getOrCreateMetric(h, labels), nil
126+
return m.getOrCreateMetricWithLabels(h, labels), nil
127127
}
128128

129129
// WithLabelValues works as GetMetricWithLabelValues, but panics if an error
@@ -171,7 +171,7 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
171171
if err != nil {
172172
return false
173173
}
174-
return m.deleteByHash(h, lvs)
174+
return m.deleteByHashWithLabelValues(h, lvs)
175175
}
176176

177177
// Delete deletes the metric where the variable labels are the same as those
@@ -193,21 +193,40 @@ func (m *MetricVec) Delete(labels Labels) bool {
193193
return false
194194
}
195195

196-
return m.deleteByHash(h, labels)
196+
return m.deleteByHashWithLabels(h, labels)
197197
}
198198

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, lvs interface{}) bool {
199+
// deleteByHashWithLabelValues removes the metric from the hash bucket h. If
200+
// there are multiple matches in the bucket, use lvs to select a metric and
201+
// remove only that metric.
202+
func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
205203
metrics, ok := m.children[h]
206204
if !ok {
207205
return false
208206
}
209207

210-
i := m.findMetric(metrics, lvs)
208+
i := m.findMetricWithLabelValues(metrics, lvs)
209+
if i >= len(metrics) {
210+
return false
211+
}
212+
213+
if len(metrics) > 1 {
214+
m.children[h] = append(metrics[:i], metrics[i+1:]...)
215+
} else {
216+
delete(m.children, h)
217+
}
218+
return true
219+
}
220+
221+
// deleteByHashWithLabels removes the metric from the hash bucket h. If there
222+
// are multiple matches in the bucket, use lvs to select a metric and remove
223+
// only that metric.
224+
func (m *MetricVec) deleteByHashWithLabels(h uint64, labels Labels) bool {
225+
metrics, ok := m.children[h]
226+
if !ok {
227+
return false
228+
}
229+
i := m.findMetricWithLabels(metrics, labels)
211230
if i >= len(metrics) {
212231
return false
213232
}
@@ -258,119 +277,128 @@ func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
258277
return h, nil
259278
}
260279

261-
// getOrCreateMetric retrieves the metric by hash and label value or creates it
262-
// and returns the new one.
280+
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
281+
// or creates it and returns the new one.
263282
//
264-
// lvs MUST be of type Labels or []string or this method will panic.
283+
// This function holds the mutex.
284+
func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string) Metric {
285+
m.mtx.RLock()
286+
metric, ok := m.getMetricWithLabelValues(hash, lvs)
287+
m.mtx.RUnlock()
288+
if ok {
289+
return metric
290+
}
291+
292+
m.mtx.Lock()
293+
defer m.mtx.Unlock()
294+
metric, ok = m.getMetricWithLabelValues(hash, lvs)
295+
if !ok {
296+
// Copy to avoid allocation in case wo don't go down this code path.
297+
copiedLVs := make([]string, len(lvs))
298+
copy(copiedLVs, lvs)
299+
metric = m.newMetric(copiedLVs...)
300+
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: copiedLVs, metric: metric})
301+
}
302+
return metric
303+
}
304+
305+
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
306+
// or creates it and returns the new one.
265307
//
266308
// This function holds the mutex.
267-
func (m *MetricVec) getOrCreateMetric(hash uint64, lvs interface{}) Metric {
309+
func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metric {
268310
m.mtx.RLock()
269-
metric, ok := m.getMetric(hash, lvs)
311+
metric, ok := m.getMetricWithLabels(hash, labels)
270312
m.mtx.RUnlock()
271313
if ok {
272314
return metric
273315
}
274316

275317
m.mtx.Lock()
276318
defer m.mtx.Unlock()
277-
metric, ok = m.getMetric(hash, lvs)
319+
metric, ok = m.getMetricWithLabels(hash, labels)
278320
if !ok {
279-
lvs := m.copyLabelValues(lvs)
321+
lvs := m.extractLabelValues(labels)
280322
metric = m.newMetric(lvs...)
281323
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: lvs, metric: metric})
282324
}
283325
return metric
284326
}
285327

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) {
328+
// getMetricWithLabelValues gets a metric while handling possible collisions in
329+
// the hash space. Must be called while holding read mutex.
330+
func (m *MetricVec) getMetricWithLabelValues(h uint64, lvs []string) (Metric, bool) {
291331
metrics, ok := m.children[h]
292332
if ok {
293-
return m.selectMetric(metrics, lvs)
333+
if i := m.findMetricWithLabelValues(metrics, lvs); i < len(metrics) {
334+
return metrics[i].metric, true
335+
}
294336
}
295-
296337
return nil, false
297338
}
298339

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
340+
// getMetricWithLabels gets a metric while handling possible collisions in
341+
// the hash space. Must be called while holding read mutex.
342+
func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool) {
343+
metrics, ok := m.children[h]
344+
if ok {
345+
if i := m.findMetricWithLabels(metrics, labels); i < len(metrics) {
346+
return metrics[i].metric, true
347+
}
304348
}
305-
306349
return nil, false
307350
}
308351

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 {
352+
// findMetricWithLabelValues returns the index of the matching metric or
353+
// len(metrics) if not found.
354+
func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, lvs []string) int {
312355
for i, metric := range metrics {
313-
if m.matchLabels(metric.values, lvs) {
356+
if m.matchLabelValues(metric.values, lvs) {
314357
return i
315358
}
316359
}
317-
318360
return len(metrics)
319361
}
320362

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-
}
363+
// findMetricWithLabels returns the index of the matching metric or len(metrics)
364+
// if not found.
365+
func (m *MetricVec) findMetricWithLabels(metrics []metricWithLabelValues, labels Labels) int {
366+
for i, metric := range metrics {
367+
if m.matchLabels(metric.values, labels) {
368+
return i
332369
}
370+
}
371+
return len(metrics)
372+
}
333373

334-
return true
335-
case Labels:
336-
if len(lvs) != len(values) {
374+
func (m *MetricVec) matchLabelValues(values []string, lvs []string) bool {
375+
if len(values) != len(lvs) {
376+
return false
377+
}
378+
for i, v := range values {
379+
if v != lvs[i] {
337380
return false
338381
}
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")
352382
}
383+
return true
353384
}
354385

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]
386+
func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
387+
if len(labels) != len(values) {
388+
return false
389+
}
390+
for i, k := range m.desc.variableLabels {
391+
if values[i] != labels[k] {
392+
return false
370393
}
371-
default:
372-
panic(fmt.Sprintf("unsupported type for lvs: %#v", lvs))
373394
}
395+
return true
396+
}
374397

398+
func (m *MetricVec) extractLabelValues(labels Labels) []string {
399+
labelValues := make([]string, len(labels))
400+
for i, k := range m.desc.variableLabels {
401+
labelValues[i] = labels[k]
402+
}
375403
return labelValues
376404
}

prometheus/vec_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func TestCounterVecEndToEndWithCollision(t *testing.T) {
225225
t.Errorf("got label value %q, want %q", got, want)
226226
}
227227
if got, want := m.GetCounter().GetValue(), 1.; got != want {
228-
t.Errorf("got value %d, want %d", got, want)
228+
t.Errorf("got value %f, want %f", got, want)
229229
}
230230
m.Reset()
231231
if err := vec.WithLabelValues("!0IC=VloaY").Write(m); err != nil {
@@ -235,7 +235,7 @@ func TestCounterVecEndToEndWithCollision(t *testing.T) {
235235
t.Errorf("got label value %q, want %q", got, want)
236236
}
237237
if got, want := m.GetCounter().GetValue(), 2.; got != want {
238-
t.Errorf("got value %d, want %d", got, want)
238+
t.Errorf("got value %f, want %f", got, want)
239239
}
240240
}
241241

0 commit comments

Comments
 (0)