Skip to content

Commit

Permalink
Add reserved_words counter (lyft#168)
Browse files Browse the repository at this point in the history
We've run into an issue internally where certain applications are
emitting stats with reserved words. We don't want to panic or otherwise
harm the app, but we do want to be able to alert / notify owners when
this is happening.

We create an additional counter here to track how often reserved_tags
are being sent to statsd without blocking them.
  • Loading branch information
theevocater authored May 16, 2024
1 parent 6d38a35 commit 49e70f1
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 4 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ jobs:
strategy:
matrix:
go-version:
- 1.19.x
- 1.20.x
- 1.22.x

name: run-tests
runs-on: ubuntu-latest
Expand Down
1 change: 0 additions & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ linters:
- gosimple
- govet
- ineffassign
- megacheck
- misspell
- nakedret
- revive
Expand Down
2 changes: 1 addition & 1 deletion stat_handler_wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestHTTPHandler_WrapResponse(t *testing.T) {

h := NewStatHandler(
NewStore(NewNullSink(), false),
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})).(*httpHandler)
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})).(*httpHandler)

for i, test := range tests {
tc := test
Expand Down
22 changes: 22 additions & 0 deletions stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,17 @@ type statStore struct {
sink Sink
}

var ReservedTagWords = map[string]bool{"asg": true, "az": true, "backend": true, "canary": true, "host": true, "period": true, "region": true, "shard": true, "window": true, "source": true, "project": true, "facet": true, "envoyservice": true}

func (s *statStore) validateTags(tags map[string]string) {
for k := range tags {
if _, ok := ReservedTagWords[k]; ok {
// Keep track of how many times a reserved tag is used
s.NewCounter("reserved_tag").Inc()
}
}
}

func (s *statStore) StartContext(ctx context.Context, ticker *time.Ticker) {
for {
select {
Expand Down Expand Up @@ -403,6 +414,7 @@ func (s *statStore) Scope(name string) Scope {
}

func (s *statStore) ScopeWithTags(name string, tags map[string]string) Scope {
s.validateTags(tags)
return newSubScope(s, name, tags)
}

Expand All @@ -422,6 +434,7 @@ func (s *statStore) NewCounter(name string) Counter {
}

func (s *statStore) NewCounterWithTags(name string, tags map[string]string) Counter {
s.validateTags(tags)
return s.newCounter(tagspkg.SerializeTags(name, tags))
}

Expand All @@ -438,6 +451,7 @@ func (s *statStore) NewPerInstanceCounter(name string, tags map[string]string) C
if _, found := tags["_f"]; found {
return s.NewCounterWithTags(name, tags)
}
s.validateTags(tags)
return s.newCounterWithTagSet(name, tagspkg.TagSet(nil).MergePerInstanceTags(tags))
}

Expand All @@ -457,6 +471,7 @@ func (s *statStore) NewGauge(name string) Gauge {
}

func (s *statStore) NewGaugeWithTags(name string, tags map[string]string) Gauge {
s.validateTags(tags)
return s.newGauge(tagspkg.SerializeTags(name, tags))
}

Expand All @@ -471,6 +486,7 @@ func (s *statStore) NewPerInstanceGauge(name string, tags map[string]string) Gau
if _, found := tags["_f"]; found {
return s.NewGaugeWithTags(name, tags)
}
s.validateTags(tags)
return s.newGaugeWithTagSet(name, tagspkg.TagSet(nil).MergePerInstanceTags(tags))
}

Expand All @@ -490,6 +506,7 @@ func (s *statStore) NewMilliTimer(name string) Timer {
}

func (s *statStore) NewMilliTimerWithTags(name string, tags map[string]string) Timer {
s.validateTags(tags)
return s.newTimer(tagspkg.SerializeTags(name, tags), time.Millisecond)
}

Expand All @@ -498,6 +515,7 @@ func (s *statStore) NewTimer(name string) Timer {
}

func (s *statStore) NewTimerWithTags(name string, tags map[string]string) Timer {
s.validateTags(tags)
return s.newTimer(tagspkg.SerializeTags(name, tags), time.Microsecond)
}

Expand All @@ -512,6 +530,7 @@ func (s *statStore) NewPerInstanceTimer(name string, tags map[string]string) Tim
if _, found := tags["_f"]; found {
return s.NewTimerWithTags(name, tags)
}
s.validateTags(tags)
return s.newTimerWithTagSet(name, tagspkg.TagSet(nil).MergePerInstanceTags(tags), time.Microsecond)
}

Expand All @@ -522,6 +541,7 @@ func (s *statStore) NewPerInstanceMilliTimer(name string, tags map[string]string
if _, found := tags["_f"]; found {
return s.NewMilliTimerWithTags(name, tags)
}
s.validateTags(tags)
return s.newTimerWithTagSet(name, tagspkg.TagSet(nil).MergePerInstanceTags(tags), time.Millisecond)
}

Expand All @@ -540,6 +560,7 @@ func (s *subScope) Scope(name string) Scope {
}

func (s *subScope) ScopeWithTags(name string, tags map[string]string) Scope {
s.registry.validateTags(tags)
return &subScope{
registry: s.registry,
name: joinScopes(s.name, name),
Expand Down Expand Up @@ -599,6 +620,7 @@ func (s *subScope) NewMilliTimerWithTags(name string, tags map[string]string) Ti
}

func (s *subScope) NewPerInstanceMilliTimer(name string, tags map[string]string) Timer {
s.registry.validateTags(tags)
return s.registry.newTimerWithTagSet(joinScopes(s.name, name),
s.tags.MergePerInstanceTags(tags), time.Millisecond)
}
Expand Down
27 changes: 27 additions & 0 deletions stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,33 @@ func TestStatsStartContext(_ *testing.T) {
wg.Wait()
}

// Ensure we create a counter and increment it for reserved tags
func TestValidateTags(t *testing.T) {
// Ensure we don't create a counter without reserved tags
sink := &testStatSink{}
store := NewStore(sink, true)
store.NewCounter("test").Inc()
store.Flush()

expected := "test:1|c"
counter := sink.record
if !strings.Contains(counter, expected) {
t.Error("wanted counter value of test:1|c, got", counter)
}

// A reserved tag should trigger adding the reserved_tag counter
sink = &testStatSink{}
store = NewStore(sink, true)
store.NewCounterWithTags("test", map[string]string{"host": "i"}).Inc()
store.Flush()

expected = "reserved_tag:1|c\ntest.__host=i:1|c"
counter = sink.record
if !strings.Contains(counter, expected) {
t.Error("wanted counter value of test.___f=i:1|c, got", counter)
}
}

// Ensure timers and timespans are working
func TestTimer(t *testing.T) {
testDuration := time.Duration(9800000)
Expand Down

0 comments on commit 49e70f1

Please sign in to comment.