Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce allocations #14

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ func Handle(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), time.Second)
defer cancel()

call := tracer.Fetch(req.Context()).Start().Mark(req.Header.Get("X-Request-Id"))
call := tracer.Fetch(req.Context()).Start(req.Header.Get("X-Request-Id"))
defer call.Stop()

...

call.Checkpoint().Mark("serialize")
call.Checkpoint("serialize")
data := FetchData(ctx, req.Body)

call.Checkpoint().Mark("store")
call.Checkpoint("store")
if err := StoreIntoDatabase(ctx, data); err != nil {
http.Error(rw,
http.StatusText(http.StatusInternalServerError),
Expand Down
2 changes: 1 addition & 1 deletion context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ func Fetch(ctx context.Context) *Trace {
return trace
}

func Inject(ctx context.Context, stack []*Call) context.Context {
func Inject(ctx context.Context, stack []Call) context.Context {
return context.WithValue(ctx, key{}, &Trace{stack: stack})
}
4 changes: 2 additions & 2 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ func TestContext(t *testing.T) {
if Fetch(context.Background()) != nil {
t.Error("expected nil")
}
Fetch(context.Background()).Start().Mark("no panic").Stop()
Fetch(context.Background()).Start("no panic").Stop()
})
t.Run("fetch injected", func(t *testing.T) {
ctx := Inject(context.Background(), nil)
if Fetch(ctx) == nil {
t.Error("unexpected nil")
}
Fetch(ctx).Start().Mark("allocation").Stop()
Fetch(ctx).Start("allocation").Stop()
})
}

Expand Down
8 changes: 4 additions & 4 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func Example() {

func InjectTracer(handler http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
req = req.WithContext(tracer.Inject(req.Context(), make([]*tracer.Call, 0, 2)))
req = req.WithContext(tracer.Inject(req.Context(), make([]tracer.Call, 0, 2)))
handler.ServeHTTP(rw, req)
})
}
Expand All @@ -64,12 +64,12 @@ func Handle(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithTimeout(req.Context(), time.Second)
defer cancel()

call := tracer.Fetch(req.Context()).Start().Mark(req.Header.Get("X-Request-Id"))
call := tracer.Fetch(req.Context()).Start(req.Header.Get("X-Request-Id"))
defer call.Stop()

time.Sleep(time.Millisecond)

call.Checkpoint().Mark("serialize")
call.Checkpoint("serialize")
data, err := FetchData(ctx, req.Body)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
Expand All @@ -78,7 +78,7 @@ func Handle(rw http.ResponseWriter, req *http.Request) {

time.Sleep(time.Millisecond)

call.Checkpoint().Mark("store")
call.Checkpoint("store")
if err := StoreIntoDatabase(ctx, data); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
Expand Down
18 changes: 9 additions & 9 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ func callerC() CallerInfo {
}

func traceRoot(ctx context.Context) {
call := Fetch(ctx).Start().Mark("root")
call := Fetch(ctx).Start("root")
defer call.Stop()

call.Checkpoint().Mark("checkpointA")
call.Checkpoint("checkpointA")
traceA(ctx)

call.Checkpoint().Mark("checkpointB")
call.Checkpoint("checkpointB")
traceB(ctx)
}

func traceA(ctx context.Context) {
call := Fetch(ctx).Start().Mark("A")
call := Fetch(ctx).Start("A")
defer call.Stop()

call.Checkpoint().Mark("checkpointA1")
call.Checkpoint("checkpointA1")
traceA1(ctx)

call.Checkpoint().Mark("checkpointA2")
call.Checkpoint("checkpointA2")
traceA2(ctx)
}

Expand All @@ -55,13 +55,13 @@ func traceA2(ctx context.Context) {
}

func traceB(ctx context.Context) {
call := Fetch(ctx).Start().Mark("B")
call := Fetch(ctx).Start("B")
defer call.Stop()

call.Checkpoint().Mark("checkpointB1")
call.Checkpoint("checkpointB1")
traceB1(ctx)

call.Checkpoint().Mark("checkpointB2")
call.Checkpoint("checkpointB2")
func(ctx context.Context) {
defer Fetch(ctx).Start().Stop()
}(ctx)
Expand Down
76 changes: 38 additions & 38 deletions tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,29 @@ import (
)

type Trace struct {
stack []*Call
stack []Call
allocates int
}

func (trace *Trace) Start() *Call {
func (trace *Trace) Start(labels ...string) ptr {
if trace == nil {
return nil
return ptr{}
}

var (
id string
tags []string
)
if len(labels) > 0 {
id = labels[0]
tags = labels[1:]
}
call := Call{caller: Caller(3), start: time.Now(), id: id, tags: tags}
if len(trace.stack) == cap(trace.stack) {
trace.allocates++
}
call := &Call{caller: Caller(3), start: time.Now()}
trace.stack = append(trace.stack, call)
return call
return ptr{trace, len(trace.stack) - 1}
}

func (trace *Trace) String() string {
Expand Down Expand Up @@ -71,50 +79,42 @@ type Call struct {
caller CallerInfo
start, stop time.Time
id string
checkpoints []*Checkpoint
tags []string
checkpoints []Checkpoint
allocates int
}

func (call *Call) Checkpoint() *Checkpoint {
if call == nil {
return nil
}

checkpoint := &Checkpoint{timestamp: time.Now()}
if len(call.checkpoints) == cap(call.checkpoints) {
call.allocates++
}
call.checkpoints = append(call.checkpoints, checkpoint)

return checkpoint
type Checkpoint struct {
id string
tags []string
timestamp time.Time
}

func (call *Call) Mark(id string) *Call {
if call == nil {
return nil
}

call.id = id
return call
type ptr struct {
*Trace
int
}

func (call *Call) Stop() {
if call == nil {
return
func (call ptr) Checkpoint(labels ...string) {
var (
id string
tags []string
)
if len(labels) > 0 {
id = labels[0]
tags = labels[1:]
}

call.stop = time.Now()
}

type Checkpoint struct {
id string
timestamp time.Time
checkpoint := Checkpoint{id: id, tags: tags, timestamp: time.Now()}
if len(call.stack[call.int].checkpoints) == cap(call.stack[call.int].checkpoints) {
call.stack[call.int].allocates++
}
call.stack[call.int].checkpoints = append(call.stack[call.int].checkpoints, checkpoint)
}

func (checkpoint *Checkpoint) Mark(id string) {
if checkpoint == nil {
func (call ptr) Stop() {
if call.Trace == nil {
return
}

checkpoint.id = id
call.stack[call.int].stop = time.Now()
}
36 changes: 8 additions & 28 deletions tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
)

func TestTrace_Start(t *testing.T) {
(*Trace)(nil).Start().Mark("no panic")
(&Trace{}).Start().Mark("one allocation")
(*Trace)(nil).Start("no panic")
(&Trace{}).Start("one allocation")
}

func TestTrace_String(t *testing.T) {
Expand All @@ -26,48 +26,28 @@ func TestTrace_String(t *testing.T) {
t.Errorf("\n expected: %+#v \n obtained: %+#v", expected, obtained)
}

call := trace.Start().Mark("fn call")
call.Checkpoint().Mark("checkpoint")
call := trace.Start("fn call")
call.Checkpoint("checkpoint")
call.Stop()
if expected, obtained := "allocates at call stack: 1", trace.String(); !strings.Contains(obtained, expected) {
t.Errorf("\n expected: %+#v \n obtained: %+#v", expected, obtained)
}
})
}

func TestCall_Checkpoint(t *testing.T) {
(*Call)(nil).Checkpoint().Mark("no panic")
(&Call{}).Checkpoint().Mark("one allocation")
}

func TestCall_Mark(t *testing.T) {
(*Call)(nil).Mark("no panic")
(&Call{}).Mark("by id")
}

func TestCall_Stop(t *testing.T) {
(*Call)(nil).Mark("no panic").Stop()
(&Call{}).Mark("success").Stop()
}

func TestCheckpoint_Mark(t *testing.T) {
(*Checkpoint)(nil).Mark("no panic")
(&Checkpoint{}).Mark("by id")
}

// BenchmarkTracing/silent-12 200000 7755 ns/op 1816 B/op 24 allocs/op
// BenchmarkTracing/full-12 200000 8880 ns/op 3944 B/op 45 allocs/op
// BenchmarkTracing/silent-12 200000 7298 ns/op 2336 B/op 18 allocs/op
// BenchmarkTracing/full-12 200000 8918 ns/op 4464 B/op 39 allocs/op
func BenchmarkTracing(b *testing.B) {
b.Run("silent", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
traceRoot(Inject(context.Background(), make([]*Call, 0, 9)))
traceRoot(Inject(context.Background(), make([]Call, 0, 9)))
}
})
b.Run("full", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ctx := Inject(context.Background(), make([]*Call, 0, 9))
ctx := Inject(context.Background(), make([]Call, 0, 9))
traceRoot(ctx)
_ = Fetch(ctx).String()
}
Expand Down