Skip to content

Commit

Permalink
metrics: copy tags list before serializing (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmoylan authored Jan 25, 2019
1 parent 418f3ad commit b5019bc
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 7 deletions.
20 changes: 13 additions & 7 deletions metrics/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ func (r *rootRegistry) Each(f MetricVisitor) {
name = metricWithTags.name
tags = make(Tags, len(metricWithTags.tags))
copy(tags, metricWithTags.tags)
sort.Sort(tags)
} else {
// Metric was added to rcrowley registry outside of our registry.
// This is likely a go runtime metric (nothing else is exposed).
Expand All @@ -300,7 +299,7 @@ func (r *rootRegistry) Each(f MetricVisitor) {
}

func (r *rootRegistry) Unregister(name string, tags ...Tag) {
metricID := toMetricTagsID(name, tags)
metricID := toMetricTagsID(name, newSortedTags(tags))
r.registry.Unregister(string(metricID))
}

Expand Down Expand Up @@ -341,11 +340,12 @@ func DefaultSample() metrics.Sample {
}

func (r *rootRegistry) registerMetric(name string, tags Tags) string {
metricID := toMetricTagsID(name, tags)
sortedTags := newSortedTags(tags)
metricID := toMetricTagsID(name, sortedTags)
r.idToMetricMutex.Lock()
r.idToMetricWithTags[metricID] = metricWithTags{
name: name,
tags: tags,
tags: sortedTags,
}
r.idToMetricMutex.Unlock()
return string(metricID)
Expand All @@ -364,10 +364,9 @@ type metricTagsID string

// toMetricTagsID generates the metricTagsID identifier for the metricWithTags. A unique {metricName, set<Tag>} input will
// generate a unique output. This implementation tries to minimize memory allocation and runtime.
// 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
// result in the same ID, they must sort the Tags before providing them to this function).
func toMetricTagsID(name string, tags Tags) metricTagsID {
// TODO(maybe): Ensure tags is already sorted when it comes in so we can remove this.
sort.Sort(tags)

// calculate how large to make our byte buffer below
bufSize := len(name)
for _, t := range tags {
Expand All @@ -382,3 +381,10 @@ func toMetricTagsID(name string, tags Tags) metricTagsID {
}
return metricTagsID(buf.Bytes())
}

// newSortedTags copies the tag slice before sorting so that in-place mutation does not affect the input slice.
func newSortedTags(tags Tags) Tags {
tagsCopy := append(tags[:0:0], tags...)
sort.Sort(tagsCopy)
return tagsCopy
}
10 changes: 10 additions & 0 deletions metrics/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ func TestMetricsWithTags(t *testing.T) {
assert.Equal(t, wantTags, gotTags)
}

func TestMetricDoesNotMutateInputTagSlice(t *testing.T) {
root := metrics.NewRootMetricsRegistry()

unsortedTags := metrics.Tags{metrics.MustNewTag("b", "b"), metrics.MustNewTag("a", "a")}

root.Counter("my-counter", unsortedTags...).Inc(1)

assert.Equal(t, metrics.Tags{metrics.MustNewTag("b", "b"), metrics.MustNewTag("a", "a")}, unsortedTags)
}

// Prefix should be used as provided (no case conversion/normalization), while tags should always be converted to
// lowercase.
func TestMetricsCasing(t *testing.T) {
Expand Down

0 comments on commit b5019bc

Please sign in to comment.