Skip to content

Commit b5019bc

Browse files
authored
metrics: copy tags list before serializing (#143)
1 parent 418f3ad commit b5019bc

2 files changed

Lines changed: 23 additions & 7 deletions

File tree

metrics/registry.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ func (r *rootRegistry) Each(f MetricVisitor) {
283283
name = metricWithTags.name
284284
tags = make(Tags, len(metricWithTags.tags))
285285
copy(tags, metricWithTags.tags)
286-
sort.Sort(tags)
287286
} else {
288287
// Metric was added to rcrowley registry outside of our registry.
289288
// This is likely a go runtime metric (nothing else is exposed).
@@ -300,7 +299,7 @@ func (r *rootRegistry) Each(f MetricVisitor) {
300299
}
301300

302301
func (r *rootRegistry) Unregister(name string, tags ...Tag) {
303-
metricID := toMetricTagsID(name, tags)
302+
metricID := toMetricTagsID(name, newSortedTags(tags))
304303
r.registry.Unregister(string(metricID))
305304
}
306305

@@ -341,11 +340,12 @@ func DefaultSample() metrics.Sample {
341340
}
342341

343342
func (r *rootRegistry) registerMetric(name string, tags Tags) string {
344-
metricID := toMetricTagsID(name, tags)
343+
sortedTags := newSortedTags(tags)
344+
metricID := toMetricTagsID(name, sortedTags)
345345
r.idToMetricMutex.Lock()
346346
r.idToMetricWithTags[metricID] = metricWithTags{
347347
name: name,
348-
tags: tags,
348+
tags: sortedTags,
349349
}
350350
r.idToMetricMutex.Unlock()
351351
return string(metricID)
@@ -364,10 +364,9 @@ type metricTagsID string
364364

365365
// toMetricTagsID generates the metricTagsID identifier for the metricWithTags. A unique {metricName, set<Tag>} input will
366366
// generate a unique output. This implementation tries to minimize memory allocation and runtime.
367+
// The ID is created by adding the tags in the order they are provided (this means that, if the caller wants a specific set of tags to always
368+
// result in the same ID, they must sort the Tags before providing them to this function).
367369
func toMetricTagsID(name string, tags Tags) metricTagsID {
368-
// TODO(maybe): Ensure tags is already sorted when it comes in so we can remove this.
369-
sort.Sort(tags)
370-
371370
// calculate how large to make our byte buffer below
372371
bufSize := len(name)
373372
for _, t := range tags {
@@ -382,3 +381,10 @@ func toMetricTagsID(name string, tags Tags) metricTagsID {
382381
}
383382
return metricTagsID(buf.Bytes())
384383
}
384+
385+
// newSortedTags copies the tag slice before sorting so that in-place mutation does not affect the input slice.
386+
func newSortedTags(tags Tags) Tags {
387+
tagsCopy := append(tags[:0:0], tags...)
388+
sort.Sort(tagsCopy)
389+
return tagsCopy
390+
}

metrics/registry_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ func TestMetricsWithTags(t *testing.T) {
7373
assert.Equal(t, wantTags, gotTags)
7474
}
7575

76+
func TestMetricDoesNotMutateInputTagSlice(t *testing.T) {
77+
root := metrics.NewRootMetricsRegistry()
78+
79+
unsortedTags := metrics.Tags{metrics.MustNewTag("b", "b"), metrics.MustNewTag("a", "a")}
80+
81+
root.Counter("my-counter", unsortedTags...).Inc(1)
82+
83+
assert.Equal(t, metrics.Tags{metrics.MustNewTag("b", "b"), metrics.MustNewTag("a", "a")}, unsortedTags)
84+
}
85+
7686
// Prefix should be used as provided (no case conversion/normalization), while tags should always be converted to
7787
// lowercase.
7888
func TestMetricsCasing(t *testing.T) {

0 commit comments

Comments
 (0)