Skip to content

Commit

Permalink
feat: add trace function to context
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Oct 9, 2021
1 parent 93b15e2 commit cc95d38
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 102 deletions.
5 changes: 5 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,11 @@ func (c *Context) IsReaderBody() bool {
return ok
}

// GetTrace get trace from context, if context without trace, new trace will be created.
func (c *Context) GetTrace() *Trace {
return GetTrace(c.Context())
}

// ServerTiming converts trace info to http response server timing
func (c *Context) ServerTiming(traceInfos TraceInfos, prefix string) {
value := traceInfos.ServerTiming(prefix)
Expand Down
4 changes: 4 additions & 0 deletions df.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import (
"github.com/vicanso/hes"
)

type ContextKey string

const ContextTraceKey ContextKey = "contextTrace"

var (
methods = []string{
http.MethodGet,
Expand Down
87 changes: 11 additions & 76 deletions elton.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -91,13 +90,7 @@ type (
functionInfos map[uintptr]string
ctxPool sync.Pool
}
// TraceInfo trace's info
TraceInfo struct {
Name string `json:"name,omitempty"`
Duration time.Duration `json:"duration,omitempty"`
}
// TraceInfos trace infos
TraceInfos []*TraceInfo

// Router router
Router struct {
Method string `json:"method,omitempty"`
Expand Down Expand Up @@ -322,10 +315,10 @@ func (e *Elton) Handle(method, path string, handlerList ...Handler) *Elton {
maxMid := len(mids)
maxNext := maxMid + len(handlerList)
index := -1
var traceInfos TraceInfos
var trace *Trace
if e.EnableTrace {
// TODO 复用traceInfos
traceInfos = make(TraceInfos, 0, maxNext)
trace = NewTrace()
c.WithContext(context.WithValue(c.Context(), ContextTraceKey, trace))
}
c.Next = func() error {
// 如果已设置响应数据,则不再执行后续的中间件
Expand All @@ -345,7 +338,7 @@ func (e *Elton) Handle(method, path string, handlerList ...Handler) *Elton {
} else {
fn = mids[index]
}
if traceInfos == nil {
if trace == nil {
return fn(c)
}
fnName := e.GetFunctionName(fn)
Expand All @@ -356,25 +349,20 @@ func (e *Elton) Handle(method, path string, handlerList ...Handler) *Elton {
startedAt := time.Now()

traceInfo := &TraceInfo{
Name: fnName,
Name: fnName,
Middleware: true,
}
// 先添加至slice中,保证顺序
traceInfos = append(traceInfos, traceInfo)
trace.Add(traceInfo)
err := fn(c)
// 完成后计算时长(前面的中间件包括后面中间件的处理时长)
traceInfo.Duration = time.Since(startedAt)
return err
}
err := c.Next()
if traceInfos != nil {
max := len(traceInfos)
for i, traceInfo := range traceInfos {
if i < max-1 {
// 计算真实耗时(不包括后面中间件处理时长)
traceInfo.Duration -= traceInfos[i+1].Duration
}
}
e.EmitTrace(c, traceInfos)
if trace != nil {
trace.Calculate()
e.EmitTrace(c, trace.Infos)
}
if err != nil {
e.EmitError(c, err)
Expand Down Expand Up @@ -705,56 +693,3 @@ func Compose(handlerList ...Handler) Handler {
return c.Next()
}
}

func getMs(ns int) string {
microSecond := int(time.Microsecond)
milliSecond := int(time.Millisecond)
if ns < microSecond {
return "0"
}

// 计算ms的位
ms := ns / milliSecond
prefix := strconv.Itoa(ms)

// 计算micro seconds
offset := (ns % milliSecond) / microSecond
// 如果小于10,不展示小数点(取小数点两位)
unit := 10
if offset < unit {
return prefix
}
// 如果小于100,补一位0
if offset < 100 {
return prefix + ".0" + strconv.Itoa(offset/unit)
}
return prefix + "." + strconv.Itoa(offset/unit)
}

// ServerTiming return server timing with prefix
func (traceInfos TraceInfos) ServerTiming(prefix string) string {
size := len(traceInfos)
if size == 0 {
return ""
}

// 转换为 http server timing
s := new(strings.Builder)
// 每一个server timing长度预估为30
s.Grow(30 * size)
for i, traceInfo := range traceInfos {
v := traceInfo.Duration.Nanoseconds()
s.WriteString(prefix)
s.WriteString(strconv.Itoa(i))
s.Write(ServerTimingDur)
s.WriteString(getMs(int(v)))
s.Write(ServerTimingDesc)
s.WriteString(traceInfo.Name)
s.Write(ServerTimingEnd)
if i != size-1 {
s.WriteRune(',')
}

}
return s.String()
}
25 changes: 0 additions & 25 deletions elton_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,31 +532,6 @@ func TestContextWithContext(t *testing.T) {
assert.Equal(ctx, c.Context())
}

func TestConvertToServerTiming(t *testing.T) {
assert := assert.New(t)
traceInfos := make(TraceInfos, 0)

t.Run("get ms", func(t *testing.T) {
assert.Equal("0", getMs(10))
assert.Equal("0.10", getMs(100000))
})

t.Run("empty trace infos", func(t *testing.T) {
assert.Empty(traceInfos.ServerTiming(""), "no trace should return nil")
})
t.Run("server timing", func(t *testing.T) {
traceInfos = append(traceInfos, &TraceInfo{
Name: "a",
Duration: time.Microsecond * 10,
})
traceInfos = append(traceInfos, &TraceInfo{
Name: "b",
Duration: time.Millisecond + time.Microsecond,
})
assert.Equal(`elton-0;dur=0.01;desc="a",elton-1;dur=1;desc="b"`, string(traceInfos.ServerTiming("elton-")))
})
}

func TestGracefulClose(t *testing.T) {
e := New()
t.Run("running 404", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.16

require (
github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.9.0
github.com/tidwall/gjson v1.9.3
github.com/vicanso/hes v0.3.9
github.com/vicanso/intranet-ip v0.0.1
github.com/vicanso/keygrip v1.2.1
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU=
github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
github.com/tidwall/gjson v1.9.0 h1:+Od7AE26jAaMgVC31cQV/Ope5iKXulNMflrlB7k+F9E=
github.com/tidwall/gjson v1.9.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E=
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/vicanso/hes v0.3.9 h1:IO21yElX6Xp3w+Lc1O2QIySrJj2jEhnl5dWbqbDYunc=
github.com/vicanso/hes v0.3.9/go.mod h1:B0l1NIQM/nYw7owAd+hyHuNnAD8Nsx0T6duhVxmXUBY=
github.com/vicanso/intranet-ip v0.0.1 h1:cYS+mExFsKqewWSuHtFwAqw/CO66GsheB/P1BPmSTx0=
Expand Down
144 changes: 144 additions & 0 deletions trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// MIT License

// Copyright (c) 2021 Tree Xie

// 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 elton

import (
"context"
"strconv"
"strings"
"time"
)

type (
// TraceInfo trace's info
TraceInfo struct {
Middleware bool `json:"-"`
Name string `json:"name,omitempty"`
Duration time.Duration `json:"duration,omitempty"`
}
// TraceInfos trace infos
TraceInfos []*TraceInfo

Trace struct {
calculateDone bool
Infos TraceInfos
}
)

// NewTrace returns a new trace
func NewTrace() *Trace {
return &Trace{
Infos: make(TraceInfos, 0),
}
}

// Add adds trace info to trace
func (t *Trace) Add(info *TraceInfo) *Trace {
t.Infos = append(t.Infos, info)
return t
}

// Calculate calculates the duration of middleware
func (t *Trace) Calculate() {
if t.calculateDone {
return
}
// middleware需要减去后面middleware的处理时长
var cur *TraceInfo
for _, item := range t.Infos {
if !item.Middleware {
continue
}
if cur != nil {
cur.Duration -= item.Duration
}
cur = item
}
t.calculateDone = true
}

func getMs(ns int) string {
microSecond := int(time.Microsecond)
milliSecond := int(time.Millisecond)
if ns < microSecond {
return "0"
}

// 计算ms的位
ms := ns / milliSecond
prefix := strconv.Itoa(ms)

// 计算micro seconds
offset := (ns % milliSecond) / microSecond
// 如果小于10,不展示小数点(取小数点两位)
unit := 10
if offset < unit {
return prefix
}
// 如果小于100,补一位0
if offset < 100 {
return prefix + ".0" + strconv.Itoa(offset/unit)
}
return prefix + "." + strconv.Itoa(offset/unit)
}

// ServerTiming return server timing with prefix
func (traceInfos TraceInfos) ServerTiming(prefix string) string {
size := len(traceInfos)
if size == 0 {
return ""
}

// 转换为 http server timing
s := new(strings.Builder)
// 每一个server timing长度预估为30
s.Grow(30 * size)
for i, traceInfo := range traceInfos {
v := traceInfo.Duration.Nanoseconds()
s.WriteString(prefix)
s.WriteString(strconv.Itoa(i))
s.Write(ServerTimingDur)
s.WriteString(getMs(int(v)))
s.Write(ServerTimingDesc)
s.WriteString(traceInfo.Name)
s.Write(ServerTimingEnd)
if i != size-1 {
s.WriteRune(',')
}

}
return s.String()
}

// GetTrace get trace from context, if context without trace, new trace will be created.
func GetTrace(ctx context.Context) *Trace {
value := ctx.Value(ContextTraceKey)
if value == nil {
return NewTrace()
}
trace, ok := value.(*Trace)
if !ok {
return NewTrace()
}
return trace
}
Loading

0 comments on commit cc95d38

Please sign in to comment.