Skip to content

Commit

Permalink
Refactor InstrumentedCall to Call in instrument package (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
robskillington authored Sep 5, 2017
1 parent 3dd04ef commit be9e53c
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 67 deletions.
45 changes: 26 additions & 19 deletions instrument.go → instrument/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,41 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package tally
package instrument

import "github.com/uber-go/tally"

const (
_resultType = "result_type"
_resultTypeError = "error"
_resultTypeSuccess = "success"
_timingFormat = "latency"
resultType = "result_type"
resultTypeError = "error"
resultTypeSuccess = "success"
timingSuffix = "latency"
)

// NewInstrumentedCall returns an InstrumentedCall with the given name
func NewInstrumentedCall(scope Scope, name string) InstrumentedCall {
return &instrumentedCall{
error: scope.Tagged(map[string]string{_resultType: _resultTypeError}).Counter(name),
success: scope.Tagged(map[string]string{_resultType: _resultTypeSuccess}).Counter(name),
timing: scope.SubScope(name).Timer(_timingFormat),
// NewCall returns a Call that instruments a function using a given scope
// and a label to name the metrics.
// The following counters are created excluding {{ and }}:
// {{name}}+result_type=success
// {{name}}+result_type=error
// The following timers are created excluding {{ and }} and replacing . with
// the scope's separator:
// {{name}}.latency
func NewCall(scope tally.Scope, name string) Call {
return &call{
error: scope.Tagged(map[string]string{resultType: resultTypeError}).Counter(name),
success: scope.Tagged(map[string]string{resultType: resultTypeSuccess}).Counter(name),
timing: scope.SubScope(name).Timer(timingSuffix),
}
}

type instrumentedCall struct {
scope Scope
success Counter
error Counter
timing Timer
type call struct {
scope tally.Scope
success tally.Counter
error tally.Counter
timing tally.Timer
}

// Exec executes the given block of code, and records whether it succeeded or
// failed, and the amount of time that it took
func (c *instrumentedCall) Exec(f ExecFn) error {
func (c *call) Exec(f ExecFn) error {
sw := c.timing.Start()

if err := f(); err != nil {
Expand Down
69 changes: 69 additions & 0 deletions instrument/call_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package instrument

import (
"errors"
"testing"

"github.com/uber-go/tally"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCallSuccess(t *testing.T) {
s := tally.NewTestScope("", nil)

err := NewCall(s, "test_call").Exec(func() error {
return nil
})
assert.Nil(t, err)

snapshot := s.Snapshot()
counters := snapshot.Counters()
timers := snapshot.Timers()

require.NotNil(t, counters["test_call+result_type=success"])
require.NotNil(t, timers["test_call.latency+"])

assert.Equal(t, int64(1), counters["test_call+result_type=success"].Value())
}

func TestCallFail(t *testing.T) {
s := tally.NewTestScope("", nil)

expected := errors.New("an error")
err := NewCall(s, "test_call").Exec(func() error {
return expected
})
assert.NotNil(t, err)
assert.Equal(t, expected, err)

snapshot := s.Snapshot()
counters := snapshot.Counters()
timers := snapshot.Timers()

require.NotNil(t, counters["test_call+result_type=error"])
require.NotNil(t, timers["test_call.latency+"])

assert.Equal(t, int64(1), counters["test_call+result_type=error"].Value())
}
31 changes: 31 additions & 0 deletions instrument/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package instrument

// ExecFn is an executable function that can be instrumented with a Call.
type ExecFn func() error

// Call allows tracking the successes, errors, and timing of functions.
type Call interface {
// Exec executes a function and records whether it succeeded or
// failed, and the amount of time that it took.
Exec(f ExecFn) error
}
38 changes: 0 additions & 38 deletions scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package tally

import (
"errors"
"math"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -314,43 +313,6 @@ func TestWriteReportLoop(t *testing.T) {
r.WaitAll()
}

func TestInstrumentedCallSuccess(t *testing.T) {
r := newTestStatsReporter()
s, closer := NewRootScope(ScopeOptions{Reporter: r}, 10)
defer closer.Close()

r.cg.Add(1)
r.tg.Add(1)
err := NewInstrumentedCall(s, "test_instrumented").Exec(func() error {
return nil
})
assert.Nil(t, err)

r.WaitAll()
assert.EqualValues(t, 1, r.counters["test_instrumented"].val)
resultType, _ := r.counters["test_instrumented"].tags["result_type"]
assert.EqualValues(t, resultType, "success")
assert.NotNil(t, r.timers["test_instrumented.latency"].val)
}

func TestInstrumentedCallFail(t *testing.T) {
r := newTestStatsReporter()
s, closer := NewRootScope(ScopeOptions{Reporter: r}, 10)
defer closer.Close()

r.cg.Add(1)
err := NewInstrumentedCall(s, "test_instrumented").Exec(func() error {
return errors.New("failure")
})
assert.NotNil(t, err)

r.WaitAll()
assert.EqualValues(t, 1, r.counters["test_instrumented"].val)
resultType, _ := r.counters["test_instrumented"].tags["result_type"]
assert.EqualValues(t, resultType, "error")
assert.Nil(t, r.timers["test_instrumented.latency"])
}

func TestCachedReportLoop(t *testing.T) {
r := newTestStatsReporter()
s, closer := NewRootScope(ScopeOptions{CachedReporter: r}, 10)
Expand Down
10 changes: 0 additions & 10 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,3 @@ type Capabilities interface {
// Tagging returns whether the reporter has the capability for tagged metrics.
Tagging() bool
}

// ExecFn will be executed in an InstrumentedCall
type ExecFn func() error

// An InstrumentedCall allows tracking the success, errors, and timing of blocks of code
type InstrumentedCall interface {
// Exec executes the given block of code, and records whether it succeeded or
// failed, and the amount of time that it took
Exec(f ExecFn) error
}

0 comments on commit be9e53c

Please sign in to comment.