Skip to content

Commit

Permalink
scope: add ScopeWithTags() method. (#43)
Browse files Browse the repository at this point in the history
This adds a `ScopeWithTags()` method that allows the caller to set
default tags on the scope. Child scopes and metrics will inherit default
tags from parents, but can still override them.

Implementation note: I reshuffled all of the non-`WithTags` methods to
delegate to `WithTags` methods to ensure the default tags are always
considered.
  • Loading branch information
diegs authored Aug 15, 2018
1 parent 1fc908d commit 4b10ad9
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 34 deletions.
101 changes: 67 additions & 34 deletions stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ type Scope interface {
// Scope creates a subscope.
Scope(name string) Scope

// ScopeWithTags creates a subscope with Tags to a store or scope. All child scopes and metrics
// will inherit these tags by default.
ScopeWithTags(name string, tags map[string]string) Scope

// Store returns the Scope's backing Store.
Store() Store

Expand Down Expand Up @@ -203,6 +207,7 @@ func NewDefaultStore() Store {
type subScope struct {
registry *statStore
name string
tags map[string]string
}

type counter struct {
Expand Down Expand Up @@ -371,10 +376,21 @@ func (s *statStore) Store() Store {
}

func (s *statStore) Scope(name string) Scope {
return subScope{registry: s, name: name}
return s.ScopeWithTags(name, nil)
}

func (s *statStore) ScopeWithTags(name string, tags map[string]string) Scope {
return subScope{registry: s, name: name, tags: tags}
}

func (s *statStore) NewCounter(name string) Counter {
return s.NewCounterWithTags(name, nil)
}

func (s *statStore) NewCounterWithTags(name string, tags map[string]string) Counter {
serializedTags := serializeTags(tags)
name = fmt.Sprintf("%s%s", name, serializedTags)

s.countersMtx.RLock()
c, ok := s.counters[name]
s.countersMtx.RUnlock()
Expand All @@ -398,12 +414,6 @@ func (s *statStore) NewCounter(name string) Counter {
s.countersMtx.Unlock()

return c

}

func (s *statStore) NewCounterWithTags(name string, tags map[string]string) Counter {
serializedTags := serializeTags(tags)
return s.NewCounter(fmt.Sprintf("%s%s", name, serializedTags))
}

func (s *statStore) NewPerInstanceCounter(name string, tags map[string]string) Counter {
Expand All @@ -414,11 +424,18 @@ func (s *statStore) NewPerInstanceCounter(name string, tags map[string]string) C
if _, found := tags["_f"]; !found {
tags["_f"] = "i"
}
serializedTags := serializeTags(tags)
return s.NewCounter(fmt.Sprintf("%s%s", name, serializedTags))

return s.NewCounterWithTags(name, tags)
}

func (s *statStore) NewGauge(name string) Gauge {
return s.NewGaugeWithTags(name, nil)
}

func (s *statStore) NewGaugeWithTags(name string, tags map[string]string) Gauge {
serializedTags := serializeTags(tags)
name = fmt.Sprintf("%s%s", name, serializedTags)

s.gaugesMtx.RLock()
g, ok := s.gauges[name]
s.gaugesMtx.RUnlock()
Expand All @@ -438,12 +455,6 @@ func (s *statStore) NewGauge(name string) Gauge {
}

return g

}

func (s *statStore) NewGaugeWithTags(name string, tags map[string]string) Gauge {
serializedTags := serializeTags(tags)
return s.NewGauge(fmt.Sprintf("%s%s", name, serializedTags))
}

func (s *statStore) NewPerInstanceGauge(name string, tags map[string]string) Gauge {
Expand All @@ -454,11 +465,18 @@ func (s *statStore) NewPerInstanceGauge(name string, tags map[string]string) Gau
if _, found := tags["_f"]; !found {
tags["_f"] = "i"
}
serializedTags := serializeTags(tags)
return s.NewGauge(fmt.Sprintf("%s%s", name, serializedTags))

return s.NewGaugeWithTags(name, tags)
}

func (s *statStore) NewTimer(name string) Timer {
return s.NewTimerWithTags(name, nil)
}

func (s *statStore) NewTimerWithTags(name string, tags map[string]string) Timer {
serializedTags := serializeTags(tags)
name = fmt.Sprintf("%s%s", name, serializedTags)

s.timersMtx.RLock()
t, ok := s.timers[name]
s.timersMtx.RUnlock()
Expand All @@ -474,12 +492,6 @@ func (s *statStore) NewTimer(name string) Timer {
s.timersMtx.Unlock()

return t

}

func (s *statStore) NewTimerWithTags(name string, tags map[string]string) Timer {
serializedTags := serializeTags(tags)
return s.NewTimer(fmt.Sprintf("%s%s", name, serializedTags))
}

func (s *statStore) NewPerInstanceTimer(name string, tags map[string]string) Timer {
Expand All @@ -490,50 +502,71 @@ func (s *statStore) NewPerInstanceTimer(name string, tags map[string]string) Tim
if _, found := tags["_f"]; !found {
tags["_f"] = "i"
}
serializedTags := serializeTags(tags)
return s.NewTimer(fmt.Sprintf("%s%s", name, serializedTags))

return s.NewTimerWithTags(name, tags)
}

func (s subScope) Scope(name string) Scope {
return &subScope{registry: s.registry, name: fmt.Sprintf("%s.%s", s.name, name)}
}

func (s subScope) ScopeWithTags(name string, tags map[string]string) Scope {
return &subScope{registry: s.registry, name: fmt.Sprintf("%s.%s", s.name, name), tags: s.mergeTags(tags)}
}

func (s subScope) Store() Store {
return s.registry
}

func (s subScope) NewCounter(name string) Counter {
return s.registry.NewCounter(fmt.Sprintf("%s.%s", s.name, name))
return s.NewCounterWithTags(name, nil)
}

func (s subScope) NewCounterWithTags(name string, tags map[string]string) Counter {
return s.registry.NewCounterWithTags(fmt.Sprintf("%s.%s", s.name, name), tags)
return s.registry.NewCounterWithTags(fmt.Sprintf("%s.%s", s.name, name), s.mergeTags(tags))
}

func (s subScope) NewPerInstanceCounter(name string, tags map[string]string) Counter {
return s.registry.NewPerInstanceCounter(fmt.Sprintf("%s.%s", s.name, name), tags)
return s.registry.NewPerInstanceCounter(fmt.Sprintf("%s.%s", s.name, name), s.mergeTags(tags))
}

func (s subScope) NewGauge(name string) Gauge {
return s.registry.NewGauge(fmt.Sprintf("%s.%s", s.name, name))
return s.NewGaugeWithTags(name, nil)
}

func (s subScope) NewGaugeWithTags(name string, tags map[string]string) Gauge {
return s.registry.NewGaugeWithTags(fmt.Sprintf("%s.%s", s.name, name), tags)
return s.registry.NewGaugeWithTags(fmt.Sprintf("%s.%s", s.name, name), s.mergeTags(tags))
}

func (s subScope) NewPerInstanceGauge(name string, tags map[string]string) Gauge {
return s.registry.NewPerInstanceGauge(fmt.Sprintf("%s.%s", s.name, name), tags)
return s.registry.NewPerInstanceGauge(fmt.Sprintf("%s.%s", s.name, name), s.mergeTags(tags))
}

func (s subScope) NewTimer(name string) Timer {
return s.registry.NewTimer(fmt.Sprintf("%s.%s", s.name, name))
return s.NewTimerWithTags(name, nil)
}

func (s subScope) NewTimerWithTags(name string, tags map[string]string) Timer {
return s.registry.NewTimerWithTags(fmt.Sprintf("%s.%s", s.name, name), tags)
return s.registry.NewTimerWithTags(fmt.Sprintf("%s.%s", s.name, name), s.mergeTags(tags))
}

func (s subScope) NewPerInstanceTimer(name string, tags map[string]string) Timer {
return s.registry.NewPerInstanceTimer(fmt.Sprintf("%s.%s", s.name, name), tags)
return s.registry.NewPerInstanceTimer(fmt.Sprintf("%s.%s", s.name, name), s.mergeTags(tags))
}

// mergeTags augments tags with all scope-level tags that are not already present.
// Modifies and returns tags directly.
func (s subScope) mergeTags(tags map[string]string) map[string]string {
if len(s.tags) == 0 {
return tags
}
if tags == nil {
tags = make(map[string]string)
}
for k, v := range s.tags {
if _, ok := tags[k]; !ok {
tags[k] = v
}
}
return tags
}
3 changes: 3 additions & 0 deletions tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ func (t tagSet) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t tagSet) Less(i, j int) bool { return t[i].dimension < t[j].dimension }

func serializeTags(tags map[string]string) string {
if len(tags) == 0 {
return ""
}
tagPairs := make([]tagPair, 0, len(tags))
for tagKey, tagValue := range tags {
tagValue = illegalTagValueChars.ReplaceAllLiteralString(tagValue, tagFailsafe)
Expand Down
40 changes: 40 additions & 0 deletions tcp_sink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,46 @@ func TestScopes(t *testing.T) {
}
}

func TestScopesWithTags(t *testing.T) {
sink := &testStatSink{}
store := NewStore(sink, true)

ascope := store.ScopeWithTags("a", map[string]string{"x": "a", "y": "a"})
bscope := ascope.ScopeWithTags("b", map[string]string{"x": "b", "z": "b"})
counter := bscope.NewCounter("c")
counter.Inc()
timer := bscope.NewTimer("t")
timer.AddValue(1)
gauge := bscope.NewGauge("g")
gauge.Set(1)
store.Flush()

expected := "a.b.t.__x=b.__y=a.__z=b:1.000000|ms\na.b.c.__x=b.__y=a.__z=b:1|c\na.b.g.__x=b.__y=a.__z=b:1|g\n"
if expected != sink.record {
t.Errorf("Expected: '%s' Got: '%s'", expected, sink.record)
}
}

func TestScopesAndMetricsWithTags(t *testing.T) {
sink := &testStatSink{}
store := NewStore(sink, true)

ascope := store.ScopeWithTags("a", map[string]string{"x": "a", "y": "a"})
bscope := ascope.Scope("b")
counter := bscope.NewCounterWithTags("c", map[string]string{"x": "m", "z": "m"})
counter.Inc()
timer := bscope.NewTimerWithTags("t", map[string]string{"x": "m", "z": "m"})
timer.AddValue(1)
gauge := bscope.NewGaugeWithTags("g", map[string]string{"x": "m", "z": "m"})
gauge.Set(1)
store.Flush()

expected := "a.b.t.__x=m.__z=m:1.000000|ms\na.b.c.__x=m.__z=m:1|c\na.b.g.__x=m.__z=m:1|g\n"
if expected != sink.record {
t.Errorf("Expected: '%s' Got: '%s'", expected, sink.record)
}
}

type testStatGenerator struct {
counter Counter
gauge Gauge
Expand Down

0 comments on commit 4b10ad9

Please sign in to comment.