Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

68
library/net/trace/BUILD Normal file
View File

@@ -0,0 +1,68 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/trace/proto:all-srcs",
],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = glob(
["*.go"],
exclude = ["*_test.go"],
),
importpath = "go-common/library/net/trace",
tags = ["manual"],
deps = [
"//library/conf/dsn:go_default_library",
"//library/conf/env:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/trace/proto:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@io_bazel_rules_go//proto/wkt:duration_go_proto",
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
"@org_golang_google_grpc//metadata:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"config_test.go",
"context_test.go",
"dapper_test.go",
"marshal_test.go",
"report_test.go",
"sample_test.go",
"span_test.go",
"tag_test.go",
"util_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
],
)

View File

@@ -0,0 +1,38 @@
### trace sdk
##### Version 4.0.0
> 1. 修改日志协议,使用 protobuf 序列化 span
> 2. 修改 interface 添加 Tag, Log Method
##### Version 3.2.2
> 1. 修复sync.Pool 未采样的trace ,未放回pool的bug
##### Version 3.2.1
> 1. 去掉链路日志最后加的换行,目前解析不需要换行
##### Version 3.2.0
> 1. use sync.Pool for new trace.
> 2. spanID use local calc.
##### Version 3.1.1
> 1. 去掉comment interface 支持
> 2. 替换comment 中分隔符为空""
##### Version 3.1.0
> 1. update user to caller
> 2. rpc client init trace info
##### Version 3.0.1
> 1. 去掉title里面的host
> 2. 过滤不采样的url
##### Version 3.0.0
> 1. 修改日志协议将一个span由之前发送两次改为只发送一次
> 2. 输出流由syslog 改为自定义实现
> 3. 将初始化配置整合到一个config里面
##### Version 2.0.0
> 1. 优化实现逻辑,将之前实现由暴露一个公开的结构体改为提供接口和实现,业务可自行实现接口
##### Version 1.0.0
> 1. 提供dapper接入sdk,对基础组件,rpc,http 调用进行封装,上报链路信息

View File

@@ -0,0 +1,9 @@
# Owner
maojian
# Author
zhoujixiang
weicheng
# Reviewer
maojian

10
library/net/trace/OWNERS Normal file
View File

@@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- maojian
- weicheng
- zhoujixiang
reviewers:
- maojian
- weicheng
- zhoujixiang

139
library/net/trace/README.md Normal file
View File

@@ -0,0 +1,139 @@
# go-common/net/trace
##### 项目简介
> 1. 提供Trace的接口规范
> 2. 提供 trace 对Tracer接口的实现供业务接入使用
##### 编译环境
> 1. 请只用golang v1.7.x以上版本编译执行。
##### 依赖包
> 1. 无
##### 编译执行
> 1. 启动接入示例
if conf.Conf.Common.Trace {
trace.Init(conf.Conf.tracer)
}
> 2. 配置参考
[tracer]
proto = "unixgram"
addr = "/var/run/dapper-collect/dapper-collect.sock"
family = "account-service"
##### 测试
> 1. 执行当前目录下所有测试文件,测试所有功能
##### 特别说明
> 1. trace 需要在conf.common配置family 和设置trace = true 应用才能接入
### trace sdk serviceName, operationName, tag 规范
注: serviceName 及之前的 family 字段, operationName 及之前的 title 字段
serviceName 使用 APP_ID 可以通过 caster 上 APP_ID 环境变量获取
#### 全局 Tag
| 名称 | 类型 | 备注 |
|----------|--------|------------------------------------------------------|
| hostname | string | 主机名 |
| ip | string | caster上使用 POD_IP 环境变量其他环境取第一个外网IP |
| zone | string | zone caster 使用 ZONE 环境变量 e.g. sh |
| region | string | region caster 使用 REGION 环境变量 e.g. region |
#### HTTP
HTTP server && client 共同 tag
| 名称 | 类型 | 备注 |
|------------------|--------|--------------------------------------------|
| http.method | string | GET、POST ... |
| http.url | string | http 完整 URL包含 query |
| http.status_code | int | http 状态码 |
HTTP server
operationName 设置:
- 非 restful API 的应用使用 URL path 部分 例如 URL http://api.bilibili.co/x/internal/user/info?mid=123 operationName 为 /x/internal/user/info
- restful API 的使用路由定义,使用 {} 代替可变部分, 例如 URL http://api.bilibili.co/x/internal/user/info/123 其中 123 为 mid则 operationName 设置为 /x/internal/user/info/{mid}
| 名称 | 类型 | 备注 |
|-----------|--------|--------------------------------------------|
| span.kind | string | 固定值 server |
| component | string | 组件名称 e.g. library/net/http/baldemaster |
HTTP client
operationName 设置:
- 请求内部非 restful 的应用可以直接设置为 URL 的 path 部分
- 请求的三方的服务或者 restful API operationName 可以直接设置为 HTTP:{Method} e.g. HTTP:GET
| 名称 | 类型 | 备注 |
|--------------|--------|--------------------------------------------------------------------------------------|
| span.kind | string | 固定值 client |
| component | string | 组件名称 e.g. library/net/http 或者 net/http |
| peer.service | string | 请求的服务APP_ID例如请求 account-service 则应该设置为 main.account.account-service |
| \_peer.sign | string | URL 的 path 部分不包含 query |
注: peer.service 不知道可以不设置_peer.sign 用于自定探测 peer.service
#### gRPC
gRPC server && client 共同 tag
gRPC server
operationName 设置:
- 使用 FullMethod https://github.com/grpc/grpc-go/blob/master/interceptor.go#L47:2
| 名称 | 类型 | 备注 |
|-----------|--------|---------------|
| span.kind | string | 固定值 server |
| component | string | 固定值 gRPC |
gRPC client
operationName 设置:
- 使用 FullMethod https://github.com/grpc/grpc-go/blob/master/interceptor.go#L47:2
| 名称 | 类型 | 备注 |
|--------------|--------|--------------------------------------------------------------------------------------|
| span.kind | string | 固定值 client |
| component | string | 固定值 gRPC |
| peer.service | string | 请求的服务APP_ID例如请求 account-service 则应该设置为 main.account.account-service |
| \_peer.sign | string | gRPC FullMethod |
#### goRPC
TODO
#### Memcache
TODO
#### Redis
TODO
#### MySQL
TODO
#### Databus
TODO
#### HBase
TODO
#### ElasticSearch
TODO

View File

@@ -0,0 +1,88 @@
package trace
import (
"flag"
"fmt"
"os"
"time"
"github.com/pkg/errors"
"go-common/library/conf/dsn"
xtime "go-common/library/time"
)
var _traceDSN = "unixgram:///var/run/dapper-collect/dapper-collect.sock"
func init() {
if v := os.Getenv("TRACE"); v != "" {
_traceDSN = v
}
flag.StringVar(&_traceDSN, "trace", _traceDSN, "trace report dsn, or use TRACE env.")
}
// Config config.
type Config struct {
// Report network e.g. unixgram, tcp, udp
Network string `dsn:"network"`
// For TCP and UDP networks, the addr has the form "host:port".
// For Unix networks, the address must be a file system path.
Addr string `dsn:"address"`
// DEPRECATED
Proto string `dsn:"network"`
// DEPRECATED
Chan int `dsn:"query.chan,"`
// Report timeout
Timeout xtime.Duration `dsn:"query.timeout,200ms"`
// DisableSample
DisableSample bool `dsn:"query.disable_sample"`
// probabilitySampling
Probability float32 `dsn:"-"`
// ProtocolVersion
ProtocolVersion int32 `dsn:"query.protocol_version,2"`
}
func parseDSN(rawdsn string) (*Config, error) {
d, err := dsn.Parse(rawdsn)
if err != nil {
return nil, errors.Wrapf(err, "trace: invalid dsn: %s", rawdsn)
}
cfg := new(Config)
if _, err = d.Bind(cfg); err != nil {
return nil, errors.Wrapf(err, "trace: invalid dsn: %s", rawdsn)
}
return cfg, nil
}
// TracerFromEnvFlag new tracer from env and flag
func TracerFromEnvFlag() (Tracer, error) {
cfg, err := parseDSN(_traceDSN)
if err != nil {
return nil, err
}
report := newReport(cfg.Network, cfg.Addr, time.Duration(cfg.Timeout), cfg.ProtocolVersion)
serviceName := serviceNameFromEnv()
return newTracer(serviceName, report, cfg), nil
}
// Init 兼容以前的 Init 写法
func Init(cfg *Config) {
serviceName := serviceNameFromEnv()
if cfg != nil {
// NOTE compatible proto field
cfg.Network = cfg.Proto
fmt.Fprintf(os.Stderr, "[deprecated] trace.Init() with conf is Deprecated, argument will be ignored. please use flag -trace or env TRACE to configure trace.\n")
report := newReport(cfg.Network, cfg.Addr, time.Duration(cfg.Timeout), cfg.ProtocolVersion)
SetGlobalTracer(newTracer(serviceName, report, cfg))
return
}
// paser config from env
cfg, err := parseDSN(_traceDSN)
if err != nil {
panic(fmt.Errorf("parse trace dsn error: %s", err))
}
report := newReport(cfg.Network, cfg.Addr, time.Duration(cfg.Timeout), cfg.ProtocolVersion)
// disable sample if uat env
cfg.DisableSample = isUATEnv()
SetGlobalTracer(newTracer(serviceName, report, cfg))
}

View File

@@ -0,0 +1,33 @@
package trace
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseDSN(t *testing.T) {
_, err := parseDSN(_traceDSN)
if err != nil {
t.Error(err)
}
}
func TestTraceFromEnvFlag(t *testing.T) {
_, err := TracerFromEnvFlag()
if err != nil {
t.Error(err)
}
}
func TestInit(t *testing.T) {
Init(nil)
_, ok := _tracer.(nooptracer)
assert.False(t, ok)
_tracer = nooptracer{}
Init(&Config{Network: "unixgram", Addr: "unixgram:///var/run/dapper-collect/dapper-collect.sock"})
_, ok = _tracer.(nooptracer)
assert.False(t, ok)
}

View File

@@ -0,0 +1,15 @@
package trace
// Trace key
const (
// 历史遗留的 key 不要轻易修改
KeyTraceID = "x1-bilispy-id"
KeyTraceSpanID = "x1-bilispy-spanid"
KeyTraceParentID = "x1-bilispy-parentid"
KeyTraceSampled = "x1-bilispy-sampled"
KeyTraceLevel = "x1-bilispy-lv"
KeyTraceCaller = "x1-bilispy-user"
// trace sdk should use bili_trace_id to get trace info after this code be merged
BiliTraceID = "bili-trace-id"
BiliTraceDebug = "bili-trace-debug"
)

View File

@@ -0,0 +1,110 @@
package trace
import (
"strconv"
"strings"
"github.com/pkg/errors"
)
const (
flagSampled = 0x01
flagDebug = 0x02
)
var (
errEmptyTracerString = errors.New("trace: cannot convert empty string to spancontext")
errInvalidTracerString = errors.New("trace: string does not match spancontext string format")
)
// SpanContext implements opentracing.SpanContext
type spanContext struct {
// traceID represents globally unique ID of the trace.
// Usually generated as a random number.
traceID uint64
// spanID represents span ID that must be unique within its trace,
// but does not have to be globally unique.
spanID uint64
// parentID refers to the ID of the parent span.
// Should be 0 if the current span is a root span.
parentID uint64
// flags is a bitmap containing such bits as 'sampled' and 'debug'.
flags byte
// probability
probability float32
// current level
level int
}
func (c spanContext) isSampled() bool {
return (c.flags & flagSampled) == flagSampled
}
func (c spanContext) isDebug() bool {
return (c.flags & flagDebug) == flagDebug
}
// IsValid check spanContext valid
func (c spanContext) IsValid() bool {
return c.traceID != 0 && c.spanID != 0
}
// emptyContext emptyContext
var emptyContext = spanContext{}
// String convert spanContext to String
// {TraceID}:{SpanID}:{ParentID}:{flags}:[extend...]
// TraceID: uint64 base16
// SpanID: uint64 base16
// ParentID: uint64 base16
// flags:
// - :0 sampled flag
// - :1 debug flag
// extend:
// sample-rate: s-{base16(BigEndian(float32))}
func (c spanContext) String() string {
base := make([]string, 4)
base[0] = strconv.FormatUint(uint64(c.traceID), 16)
base[1] = strconv.FormatUint(uint64(c.spanID), 16)
base[2] = strconv.FormatUint(uint64(c.parentID), 16)
base[3] = strconv.FormatUint(uint64(c.flags), 16)
return strings.Join(base, ":")
}
// ContextFromString parse spanContext form string
func contextFromString(value string) (spanContext, error) {
if value == "" {
return emptyContext, errEmptyTracerString
}
items := strings.Split(value, ":")
if len(items) < 4 {
return emptyContext, errInvalidTracerString
}
parseHexUint64 := func(hexs []string) ([]uint64, error) {
rets := make([]uint64, len(hexs))
var err error
for i, hex := range hexs {
rets[i], err = strconv.ParseUint(hex, 16, 64)
if err != nil {
break
}
}
return rets, err
}
rets, err := parseHexUint64(items[0:4])
if err != nil {
return emptyContext, errInvalidTracerString
}
sctx := spanContext{
traceID: rets[0],
spanID: rets[1],
parentID: rets[2],
flags: byte(rets[3]),
}
return sctx, nil
}

View File

@@ -0,0 +1,26 @@
package trace
import (
"testing"
)
func TestSpanContext(t *testing.T) {
pctx := &spanContext{
parentID: genID(),
spanID: genID(),
traceID: genID(),
flags: flagSampled,
}
if !pctx.isSampled() {
t.Error("expect sampled")
}
value := pctx.String()
t.Logf("bili-trace-id: %s", value)
pctx2, err := contextFromString(value)
if err != nil {
t.Error(err)
}
if pctx2.parentID != pctx.parentID || pctx2.spanID != pctx.spanID || pctx2.traceID != pctx.traceID || pctx2.flags != pctx.flags {
t.Errorf("wrong spancontext get %+v -> %+v", pctx, pctx2)
}
}

210
library/net/trace/dapper.go Normal file
View File

@@ -0,0 +1,210 @@
package trace
import (
"log"
"os"
"strconv"
"sync"
"time"
)
const _maxLevel = 64
func newTracer(serviceName string, report reporter, cfg *Config) Tracer {
// hard code reset probability at 0.00025, 1/4000
cfg.Probability = 0.00025
sampler := newSampler(cfg.Probability)
// default internal tags
tags := extendTag()
stdlog := log.New(os.Stderr, "trace", log.LstdFlags)
return &dapper{
cfg: cfg,
serviceName: serviceName,
propagators: map[interface{}]propagator{
HTTPFormat: httpPropagator{},
GRPCFormat: grpcPropagator{},
},
reporter: report,
sampler: sampler,
tags: tags,
pool: &sync.Pool{New: func() interface{} { return new(span) }},
stdlog: stdlog,
}
}
type dapper struct {
cfg *Config
serviceName string
tags []Tag
reporter reporter
propagators map[interface{}]propagator
pool *sync.Pool
stdlog *log.Logger
sampler sampler
}
func (d *dapper) New(operationName string, opts ...Option) Trace {
opt := defaultOption
for _, fn := range opts {
fn(&opt)
}
traceID := genID()
var sampled bool
var probability float32
if d.cfg.DisableSample {
sampled = true
probability = 1
} else {
sampled, probability = d.sampler.IsSampled(traceID, operationName)
}
pctx := spanContext{traceID: traceID}
if sampled {
pctx.flags = flagSampled
pctx.probability = probability
}
if opt.Debug {
pctx.flags |= flagDebug
return d.newSpanWithContext(operationName, pctx).SetTag(TagString(TagSpanKind, "server")).SetTag(TagBool("debug", true))
}
// 为了兼容临时为 New 的 Span 设置 span.kind
return d.newSpanWithContext(operationName, pctx).SetTag(TagString(TagSpanKind, "server"))
}
func (d *dapper) newSpanWithContext(operationName string, pctx spanContext) Trace {
sp := d.getSpan()
// is span is not sampled just return a span with this context, no need clear it
//if !pctx.isSampled() {
// sp.context = pctx
// return sp
//}
if pctx.level > _maxLevel {
// if span reach max limit level return noopspan
return noopspan{}
}
level := pctx.level + 1
nctx := spanContext{
traceID: pctx.traceID,
parentID: pctx.spanID,
flags: pctx.flags,
level: level,
}
if pctx.spanID == 0 {
nctx.spanID = pctx.traceID
} else {
nctx.spanID = genID()
}
sp.operationName = operationName
sp.context = nctx
sp.startTime = time.Now()
sp.tags = append(sp.tags, d.tags...)
return sp
}
func (d *dapper) Inject(t Trace, format interface{}, carrier interface{}) error {
// if carrier implement Carrier use direct, ignore format
carr, ok := carrier.(Carrier)
if ok {
t.Visit(carr.Set)
return nil
}
// use Built-in propagators
pp, ok := d.propagators[format]
if !ok {
return ErrUnsupportedFormat
}
carr, err := pp.Inject(carrier)
if err != nil {
return err
}
if t != nil {
t.Visit(carr.Set)
}
return nil
}
func (d *dapper) Extract(format interface{}, carrier interface{}) (Trace, error) {
sp, err := d.extract(format, carrier)
if err != nil {
return sp, err
}
// 为了兼容临时为 New 的 Span 设置 span.kind
return sp.SetTag(TagString(TagSpanKind, "server")), nil
}
func (d *dapper) extract(format interface{}, carrier interface{}) (Trace, error) {
// if carrier implement Carrier use direct, ignore format
carr, ok := carrier.(Carrier)
if !ok {
// use Built-in propagators
pp, ok := d.propagators[format]
if !ok {
return nil, ErrUnsupportedFormat
}
var err error
if carr, err = pp.Extract(carrier); err != nil {
return nil, err
}
}
contextStr := carr.Get(BiliTraceID)
if contextStr == "" {
return d.legacyExtract(carr)
}
pctx, err := contextFromString(contextStr)
if err != nil {
return nil, err
}
// NOTE: call SetTitle after extract trace
return d.newSpanWithContext("", pctx), nil
}
func (d *dapper) legacyExtract(carr Carrier) (Trace, error) {
traceIDstr := carr.Get(KeyTraceID)
if traceIDstr == "" {
return nil, ErrTraceNotFound
}
traceID, err := strconv.ParseUint(traceIDstr, 10, 64)
if err != nil {
return nil, ErrTraceCorrupted
}
sampled, _ := strconv.ParseBool(carr.Get(KeyTraceSampled))
spanID, _ := strconv.ParseUint(carr.Get(KeyTraceSpanID), 10, 64)
parentID, _ := strconv.ParseUint(carr.Get(KeyTraceSpanID), 10, 64)
pctx := spanContext{traceID: traceID, spanID: spanID, parentID: parentID}
if sampled {
pctx.flags = flagSampled
}
return d.newSpanWithContext("", pctx), nil
}
func (d *dapper) Close() error {
return d.reporter.Close()
}
func (d *dapper) report(sp *span) {
if sp.context.isSampled() {
if err := d.reporter.WriteSpan(sp); err != nil {
d.stdlog.Printf("marshal trace span error: %s", err)
}
}
d.putSpan(sp)
}
func (d *dapper) putSpan(sp *span) {
if len(sp.tags) > 32 {
sp.tags = nil
}
if len(sp.logs) > 32 {
sp.logs = nil
}
d.pool.Put(sp)
}
func (d *dapper) getSpan() *span {
sp := d.pool.Get().(*span)
sp.dapper = d
sp.childs = 0
sp.tags = sp.tags[:0]
sp.logs = sp.logs[:0]
return sp
}

View File

@@ -0,0 +1,136 @@
package trace
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/metadata"
)
type mockReport struct {
sps []*span
}
func (m *mockReport) WriteSpan(sp *span) error {
m.sps = append(m.sps, sp)
return nil
}
func (m *mockReport) Close() error {
return nil
}
func TestDapperPropagation(t *testing.T) {
t.Run("test HTTP progagation", func(t *testing.T) {
report := &mockReport{}
t1 := newTracer("service1", report, &Config{DisableSample: true})
t2 := newTracer("service2", report, &Config{DisableSample: true})
sp1 := t1.New("opt_1")
sp2 := sp1.Fork("", "opt_client")
header := make(http.Header)
t1.Inject(sp2, HTTPFormat, header)
sp3, err := t2.Extract(HTTPFormat, header)
if err != nil {
t.Fatal(err)
}
sp3.Finish(nil)
sp2.Finish(nil)
sp1.Finish(nil)
assert.Len(t, report.sps, 3)
assert.Equal(t, report.sps[2].context.parentID, uint64(0))
assert.Equal(t, report.sps[0].context.traceID, report.sps[1].context.traceID)
assert.Equal(t, report.sps[2].context.traceID, report.sps[1].context.traceID)
assert.Equal(t, report.sps[1].context.parentID, report.sps[2].context.spanID)
assert.Equal(t, report.sps[0].context.parentID, report.sps[1].context.spanID)
})
t.Run("test gRPC progagation", func(t *testing.T) {
report := &mockReport{}
t1 := newTracer("service1", report, &Config{DisableSample: true})
t2 := newTracer("service2", report, &Config{DisableSample: true})
sp1 := t1.New("opt_1")
sp2 := sp1.Fork("", "opt_client")
md := make(metadata.MD)
t1.Inject(sp2, GRPCFormat, md)
sp3, err := t2.Extract(GRPCFormat, md)
if err != nil {
t.Fatal(err)
}
sp3.Finish(nil)
sp2.Finish(nil)
sp1.Finish(nil)
assert.Len(t, report.sps, 3)
assert.Equal(t, report.sps[2].context.parentID, uint64(0))
assert.Equal(t, report.sps[0].context.traceID, report.sps[1].context.traceID)
assert.Equal(t, report.sps[2].context.traceID, report.sps[1].context.traceID)
assert.Equal(t, report.sps[1].context.parentID, report.sps[2].context.spanID)
assert.Equal(t, report.sps[0].context.parentID, report.sps[1].context.spanID)
})
t.Run("test normal", func(t *testing.T) {
report := &mockReport{}
t1 := newTracer("service1", report, &Config{Probability: 0.000000001})
sp1 := t1.New("test123")
sp1.Finish(nil)
})
t.Run("test debug progagation", func(t *testing.T) {
report := &mockReport{}
t1 := newTracer("service1", report, &Config{})
t2 := newTracer("service2", report, &Config{})
sp1 := t1.New("opt_1", EnableDebug())
sp2 := sp1.Fork("", "opt_client")
header := make(http.Header)
t1.Inject(sp2, HTTPFormat, header)
sp3, err := t2.Extract(HTTPFormat, header)
if err != nil {
t.Fatal(err)
}
sp3.Finish(nil)
sp2.Finish(nil)
sp1.Finish(nil)
assert.Len(t, report.sps, 3)
assert.Equal(t, report.sps[2].context.parentID, uint64(0))
assert.Equal(t, report.sps[0].context.traceID, report.sps[1].context.traceID)
assert.Equal(t, report.sps[2].context.traceID, report.sps[1].context.traceID)
assert.Equal(t, report.sps[1].context.parentID, report.sps[2].context.spanID)
assert.Equal(t, report.sps[0].context.parentID, report.sps[1].context.spanID)
})
}
func BenchmarkSample(b *testing.B) {
err := fmt.Errorf("test error")
report := &mockReport{}
t1 := newTracer("service1", report, &Config{})
for i := 0; i < b.N; i++ {
sp1 := t1.New("test_opt1")
sp1.SetTag(TagString("test", "123"))
sp2 := sp1.Fork("", "opt2")
sp3 := sp2.Fork("", "opt3")
sp3.SetTag(TagString("test", "123"))
sp3.Finish(nil)
sp2.Finish(&err)
sp1.Finish(nil)
}
}
func BenchmarkDisableSample(b *testing.B) {
err := fmt.Errorf("test error")
report := &mockReport{}
t1 := newTracer("service1", report, &Config{DisableSample: true})
for i := 0; i < b.N; i++ {
sp1 := t1.New("test_opt1")
sp1.SetTag(TagString("test", "123"))
sp2 := sp1.Fork("", "opt2")
sp3 := sp2.Fork("", "opt3")
sp3.SetTag(TagString("test", "123"))
sp3.Finish(nil)
sp2.Finish(&err)
sp1.Finish(nil)
}
}

View File

@@ -0,0 +1,164 @@
package trace
import (
"encoding/binary"
"fmt"
"math"
"strconv"
"time"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/duration"
"github.com/golang/protobuf/ptypes/timestamp"
protogen "go-common/library/net/trace/proto"
)
const protoVersion2 int32 = 2
func marshalSpan(sp *span, version int32) ([]byte, error) {
if version == protoVersion2 {
return marshalSpanV2(sp)
}
return marshalSpanV1(sp)
}
func marshalSpanV2(sp *span) ([]byte, error) {
protoSpan := new(protogen.Span)
protoSpan.Version = protoVersion2
protoSpan.ServiceName = sp.dapper.serviceName
protoSpan.OperationName = sp.operationName
protoSpan.TraceId = sp.context.traceID
protoSpan.SpanId = sp.context.spanID
protoSpan.ParentId = sp.context.parentID
protoSpan.SamplingProbability = sp.context.probability
protoSpan.StartTime = &timestamp.Timestamp{
Seconds: sp.startTime.Unix(),
Nanos: int32(sp.startTime.Nanosecond()),
}
protoSpan.Duration = &duration.Duration{
Seconds: int64(sp.duration / time.Second),
Nanos: int32(sp.duration % time.Second),
}
protoSpan.Tags = make([]*protogen.Tag, len(sp.tags))
for i := range sp.tags {
protoSpan.Tags[i] = toProtoTag(sp.tags[i])
}
protoSpan.Logs = sp.logs
return proto.Marshal(protoSpan)
}
func toLeagcyTag(tag Tag) *protogen.Tag {
ptag := &protogen.Tag{Key: tag.Key}
switch value := tag.Value.(type) {
case string:
ptag.Kind = protogen.Tag_STRING
ptag.Value = []byte(value)
case int:
ptag.Kind = protogen.Tag_INT
ptag.Value = []byte(strconv.FormatInt(int64(value), 10))
case int32:
ptag.Kind = protogen.Tag_INT
ptag.Value = []byte(strconv.FormatInt(int64(value), 10))
case int64:
ptag.Kind = protogen.Tag_INT
ptag.Value = []byte(strconv.FormatInt(value, 10))
case bool:
ptag.Kind = protogen.Tag_BOOL
ptag.Value = []byte(strconv.FormatBool(value))
case float32:
ptag.Kind = protogen.Tag_BOOL
ptag.Value = []byte(strconv.FormatFloat(float64(value), 'E', -1, 64))
case float64:
ptag.Kind = protogen.Tag_BOOL
ptag.Value = []byte(strconv.FormatFloat(value, 'E', -1, 64))
default:
ptag.Kind = protogen.Tag_STRING
ptag.Value = []byte((fmt.Sprintf("%v", tag.Value)))
}
return ptag
}
func toLeagcyLog(logs []*protogen.Log) []*protogen.Log {
for _, log := range logs {
if len(log.Fields) == 0 {
continue
}
log.Key = log.Fields[0].Key
log.Value = log.Fields[0].Value
}
return logs
}
func marshalSpanV1(sp *span) ([]byte, error) {
protoSpan := new(protogen.Span)
protoSpan.ServiceName = sp.dapper.serviceName
protoSpan.OperationName = sp.operationName
protoSpan.TraceId = sp.context.traceID
protoSpan.SpanId = sp.context.spanID
protoSpan.ParentId = sp.context.parentID
protoSpan.SamplingProbability = sp.context.probability
protoSpan.StartAt = sp.startTime.UnixNano()
protoSpan.FinishAt = sp.startTime.UnixNano() + int64(sp.duration)
protoSpan.Tags = make([]*protogen.Tag, len(sp.tags))
for i := range sp.tags {
protoSpan.Tags[i] = toLeagcyTag(sp.tags[i])
}
protoSpan.Logs = toLeagcyLog(sp.logs)
return proto.Marshal(protoSpan)
}
func serializeInt64(v int64) []byte {
data := make([]byte, 8)
binary.BigEndian.PutUint64(data, uint64(v))
return data
}
func serializeFloat64(v float64) []byte {
data := make([]byte, 8)
binary.BigEndian.PutUint64(data, math.Float64bits(v))
return data
}
func serializeBool(v bool) []byte {
data := make([]byte, 1)
if v {
data[0] = byte(1)
} else {
data[0] = byte(0)
}
return data
}
func toProtoTag(tag Tag) *protogen.Tag {
ptag := &protogen.Tag{Key: tag.Key}
switch value := tag.Value.(type) {
case string:
ptag.Kind = protogen.Tag_STRING
ptag.Value = []byte(value)
case int:
ptag.Kind = protogen.Tag_INT
ptag.Value = serializeInt64(int64(value))
case int32:
ptag.Kind = protogen.Tag_INT
ptag.Value = serializeInt64(int64(value))
case int64:
ptag.Kind = protogen.Tag_INT
ptag.Value = serializeInt64(value)
case bool:
ptag.Kind = protogen.Tag_BOOL
ptag.Value = serializeBool(value)
case float32:
ptag.Kind = protogen.Tag_BOOL
ptag.Value = serializeFloat64(float64(value))
case float64:
ptag.Kind = protogen.Tag_BOOL
ptag.Value = serializeFloat64(value)
default:
ptag.Kind = protogen.Tag_STRING
ptag.Value = []byte((fmt.Sprintf("%v", tag.Value)))
}
return ptag
}

View File

@@ -0,0 +1,18 @@
package trace
import (
"testing"
)
func TestMarshalSpanV1(t *testing.T) {
report := &mockReport{}
t1 := newTracer("service1", report, &Config{DisableSample: true})
sp1 := t1.New("opt_test").(*span)
sp1.SetLog(Log("hello", "test123"))
sp1.SetTag(TagString("tag1", "hell"), TagBool("booltag", true), TagFloat64("float64tag", 3.14159))
sp1.Finish(nil)
_, err := marshalSpanV1(sp1)
if err != nil {
t.Error(err)
}
}

45
library/net/trace/noop.go Normal file
View File

@@ -0,0 +1,45 @@
package trace
var (
_ Tracer = nooptracer{}
)
type nooptracer struct{}
func (n nooptracer) New(title string, opts ...Option) Trace {
return noopspan{}
}
func (n nooptracer) Inject(t Trace, format interface{}, carrier interface{}) error {
return nil
}
func (n nooptracer) Extract(format interface{}, carrier interface{}) (Trace, error) {
return noopspan{}, nil
}
type noopspan struct{}
func (n noopspan) Fork(string, string) Trace {
return noopspan{}
}
func (n noopspan) Follow(string, string) Trace {
return noopspan{}
}
func (n noopspan) Finish(err *error) {}
func (n noopspan) SetTag(tags ...Tag) Trace {
return noopspan{}
}
func (n noopspan) SetLog(logs ...LogField) Trace {
return noopspan{}
}
func (n noopspan) Visit(func(k, v string)) {}
func (n noopspan) SetTitle(string) {}
func (n noopspan) String() string { return "" }

View File

@@ -0,0 +1,17 @@
package trace
var defaultOption = option{}
type option struct {
Debug bool
}
// Option dapper Option
type Option func(*option)
// EnableDebug enable debug mode
func EnableDebug() Option {
return func(opt *option) {
opt.Debug = true
}
}

View File

@@ -0,0 +1,177 @@
package trace
import (
"net/http"
"github.com/pkg/errors"
"google.golang.org/grpc/metadata"
)
var (
// ErrUnsupportedFormat occurs when the `format` passed to Tracer.Inject() or
// Tracer.Extract() is not recognized by the Tracer implementation.
ErrUnsupportedFormat = errors.New("trace: Unknown or unsupported Inject/Extract format")
// ErrTraceNotFound occurs when the `carrier` passed to
// Tracer.Extract() is valid and uncorrupted but has insufficient
// information to extract a Trace.
ErrTraceNotFound = errors.New("trace: Trace not found in Extract carrier")
// ErrInvalidTrace errors occur when Tracer.Inject() is asked to
// operate on a Trace which it is not prepared to handle (for
// example, since it was created by a different tracer implementation).
ErrInvalidTrace = errors.New("trace: Trace type incompatible with tracer")
// ErrInvalidCarrier errors occur when Tracer.Inject() or Tracer.Extract()
// implementations expect a different type of `carrier` than they are
// given.
ErrInvalidCarrier = errors.New("trace: Invalid Inject/Extract carrier")
// ErrTraceCorrupted occurs when the `carrier` passed to
// Tracer.Extract() is of the expected type but is corrupted.
ErrTraceCorrupted = errors.New("trace: Trace data corrupted in Extract carrier")
)
// BuiltinFormat is used to demarcate the values within package `trace`
// that are intended for use with the Tracer.Inject() and Tracer.Extract()
// methods.
type BuiltinFormat byte
// support format list
const (
// HTTPFormat represents Trace as HTTP header string pairs.
//
// the HTTPFormat format requires that the keys and values
// be valid as HTTP headers as-is (i.e., character casing may be unstable
// and special characters are disallowed in keys, values should be
// URL-escaped, etc).
//
// the carrier must be a `http.Header`.
HTTPFormat BuiltinFormat = iota
// GRPCFormat represents Trace as gRPC metadata.
//
// the carrier must be a `google.golang.org/grpc/metadata.MD`.
GRPCFormat
)
// Carrier propagator must convert generic interface{} to something this
// implement Carrier interface, Trace can use Carrier to represents itself.
type Carrier interface {
Set(key, val string)
Get(key string) string
}
// propagator is responsible for injecting and extracting `Trace` instances
// from a format-specific "carrier"
type propagator interface {
Inject(carrier interface{}) (Carrier, error)
Extract(carrier interface{}) (Carrier, error)
}
type httpPropagator struct{}
type httpCarrier http.Header
func (h httpCarrier) Set(key, val string) {
http.Header(h).Set(key, val)
}
func (h httpCarrier) Get(key string) string {
return http.Header(h).Get(key)
}
func (httpPropagator) Inject(carrier interface{}) (Carrier, error) {
header, ok := carrier.(http.Header)
if !ok {
return nil, ErrInvalidCarrier
}
if header == nil {
return nil, ErrInvalidTrace
}
return httpCarrier(header), nil
}
func (httpPropagator) Extract(carrier interface{}) (Carrier, error) {
header, ok := carrier.(http.Header)
if !ok {
return nil, ErrInvalidCarrier
}
if header == nil {
return nil, ErrTraceNotFound
}
return httpCarrier(header), nil
}
const legacyGRPCKey = "trace"
type grpcPropagator struct{}
type grpcCarrier map[string][]string
func (g grpcCarrier) Get(key string) string {
if v, ok := g[key]; ok && len(v) > 0 {
return v[0]
}
ts := g[legacyGRPCKey]
if len(ts) != 8 {
return ""
}
switch key {
case KeyTraceID:
return ts[0]
case KeyTraceSpanID:
return ts[1]
case KeyTraceParentID:
return ts[2]
case KeyTraceLevel:
return ts[3]
case KeyTraceSampled:
return ts[4]
case KeyTraceCaller:
return ts[5]
}
return ""
}
func (g grpcCarrier) Set(key, val string) {
ts := make([]string, 8)
g[legacyGRPCKey] = ts
switch key {
case KeyTraceID:
ts[0] = val
case KeyTraceSpanID:
ts[1] = val
case KeyTraceParentID:
ts[2] = val
case KeyTraceLevel:
ts[3] = val
case KeyTraceSampled:
ts[4] = val
case KeyTraceCaller:
ts[5] = val
default:
g[key] = append(g[key], val)
}
}
func (grpcPropagator) Inject(carrier interface{}) (Carrier, error) {
md, ok := carrier.(metadata.MD)
if !ok {
return nil, ErrInvalidCarrier
}
if md == nil {
return nil, ErrInvalidTrace
}
return grpcCarrier(md), nil
}
func (grpcPropagator) Extract(carrier interface{}) (Carrier, error) {
md, ok := carrier.(metadata.MD)
if !ok {
return nil, ErrInvalidCarrier
}
if md == nil {
return nil, ErrTraceNotFound
}
return grpcCarrier(md), nil
}

View File

@@ -0,0 +1,61 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [],
embed = [":protogen_go_proto"],
importpath = "go-common/library/net/trace/proto",
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
"@com_github_golang_protobuf//proto:go_default_library",
"@io_bazel_rules_go//proto/wkt:duration_go_proto",
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
proto_library(
name = "protogen_proto",
srcs = ["span.proto"],
tags = ["manual"],
deps = [
"@com_google_protobuf//:duration_proto",
"@com_google_protobuf//:timestamp_proto",
],
)
go_proto_library(
name = "protogen_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
importpath = "go-common/library/net/trace/proto",
proto = ":protogen_proto",
tags = ["manual"],
deps = [
"@io_bazel_rules_go//proto/wkt:duration_go_proto",
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
],
)

View File

@@ -0,0 +1,557 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/span.proto
package protogen
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import duration "github.com/golang/protobuf/ptypes/duration"
import timestamp "github.com/golang/protobuf/ptypes/timestamp"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Tag_Kind int32
const (
Tag_STRING Tag_Kind = 0
Tag_INT Tag_Kind = 1
Tag_BOOL Tag_Kind = 2
Tag_FLOAT Tag_Kind = 3
)
var Tag_Kind_name = map[int32]string{
0: "STRING",
1: "INT",
2: "BOOL",
3: "FLOAT",
}
var Tag_Kind_value = map[string]int32{
"STRING": 0,
"INT": 1,
"BOOL": 2,
"FLOAT": 3,
}
func (x Tag_Kind) String() string {
return proto.EnumName(Tag_Kind_name, int32(x))
}
func (Tag_Kind) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{0, 0}
}
type Log_Kind int32
const (
Log_STRING Log_Kind = 0
Log_INT Log_Kind = 1
Log_BOOL Log_Kind = 2
Log_FLOAT Log_Kind = 3
)
var Log_Kind_name = map[int32]string{
0: "STRING",
1: "INT",
2: "BOOL",
3: "FLOAT",
}
var Log_Kind_value = map[string]int32{
"STRING": 0,
"INT": 1,
"BOOL": 2,
"FLOAT": 3,
}
func (x Log_Kind) String() string {
return proto.EnumName(Log_Kind_name, int32(x))
}
func (Log_Kind) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{2, 0}
}
type SpanRef_RefType int32
const (
SpanRef_CHILD_OF SpanRef_RefType = 0
SpanRef_FOLLOWS_FROM SpanRef_RefType = 1
)
var SpanRef_RefType_name = map[int32]string{
0: "CHILD_OF",
1: "FOLLOWS_FROM",
}
var SpanRef_RefType_value = map[string]int32{
"CHILD_OF": 0,
"FOLLOWS_FROM": 1,
}
func (x SpanRef_RefType) String() string {
return proto.EnumName(SpanRef_RefType_name, int32(x))
}
func (SpanRef_RefType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{3, 0}
}
type Tag struct {
Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"`
Kind Tag_Kind `protobuf:"varint,2,opt,name=kind,enum=dapper.trace.Tag_Kind" json:"kind,omitempty"`
Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Tag) Reset() { *m = Tag{} }
func (m *Tag) String() string { return proto.CompactTextString(m) }
func (*Tag) ProtoMessage() {}
func (*Tag) Descriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{0}
}
func (m *Tag) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Tag.Unmarshal(m, b)
}
func (m *Tag) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Tag.Marshal(b, m, deterministic)
}
func (dst *Tag) XXX_Merge(src proto.Message) {
xxx_messageInfo_Tag.Merge(dst, src)
}
func (m *Tag) XXX_Size() int {
return xxx_messageInfo_Tag.Size(m)
}
func (m *Tag) XXX_DiscardUnknown() {
xxx_messageInfo_Tag.DiscardUnknown(m)
}
var xxx_messageInfo_Tag proto.InternalMessageInfo
func (m *Tag) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
func (m *Tag) GetKind() Tag_Kind {
if m != nil {
return m.Kind
}
return Tag_STRING
}
func (m *Tag) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
type Field struct {
Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"`
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Field) Reset() { *m = Field{} }
func (m *Field) String() string { return proto.CompactTextString(m) }
func (*Field) ProtoMessage() {}
func (*Field) Descriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{1}
}
func (m *Field) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Field.Unmarshal(m, b)
}
func (m *Field) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Field.Marshal(b, m, deterministic)
}
func (dst *Field) XXX_Merge(src proto.Message) {
xxx_messageInfo_Field.Merge(dst, src)
}
func (m *Field) XXX_Size() int {
return xxx_messageInfo_Field.Size(m)
}
func (m *Field) XXX_DiscardUnknown() {
xxx_messageInfo_Field.DiscardUnknown(m)
}
var xxx_messageInfo_Field proto.InternalMessageInfo
func (m *Field) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
func (m *Field) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
type Log struct {
Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"`
Kind Log_Kind `protobuf:"varint,2,opt,name=kind,enum=dapper.trace.Log_Kind" json:"kind,omitempty"`
Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
Timestamp int64 `protobuf:"varint,4,opt,name=timestamp" json:"timestamp,omitempty"`
Fields []*Field `protobuf:"bytes,5,rep,name=fields" json:"fields,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Log) Reset() { *m = Log{} }
func (m *Log) String() string { return proto.CompactTextString(m) }
func (*Log) ProtoMessage() {}
func (*Log) Descriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{2}
}
func (m *Log) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Log.Unmarshal(m, b)
}
func (m *Log) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Log.Marshal(b, m, deterministic)
}
func (dst *Log) XXX_Merge(src proto.Message) {
xxx_messageInfo_Log.Merge(dst, src)
}
func (m *Log) XXX_Size() int {
return xxx_messageInfo_Log.Size(m)
}
func (m *Log) XXX_DiscardUnknown() {
xxx_messageInfo_Log.DiscardUnknown(m)
}
var xxx_messageInfo_Log proto.InternalMessageInfo
func (m *Log) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
func (m *Log) GetKind() Log_Kind {
if m != nil {
return m.Kind
}
return Log_STRING
}
func (m *Log) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func (m *Log) GetTimestamp() int64 {
if m != nil {
return m.Timestamp
}
return 0
}
func (m *Log) GetFields() []*Field {
if m != nil {
return m.Fields
}
return nil
}
// SpanRef describes causal relationship of the current span to another span (e.g. 'child-of')
type SpanRef struct {
RefType SpanRef_RefType `protobuf:"varint,1,opt,name=ref_type,json=refType,enum=dapper.trace.SpanRef_RefType" json:"ref_type,omitempty"`
TraceId uint64 `protobuf:"varint,2,opt,name=trace_id,json=traceId" json:"trace_id,omitempty"`
SpanId uint64 `protobuf:"varint,3,opt,name=span_id,json=spanId" json:"span_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SpanRef) Reset() { *m = SpanRef{} }
func (m *SpanRef) String() string { return proto.CompactTextString(m) }
func (*SpanRef) ProtoMessage() {}
func (*SpanRef) Descriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{3}
}
func (m *SpanRef) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SpanRef.Unmarshal(m, b)
}
func (m *SpanRef) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SpanRef.Marshal(b, m, deterministic)
}
func (dst *SpanRef) XXX_Merge(src proto.Message) {
xxx_messageInfo_SpanRef.Merge(dst, src)
}
func (m *SpanRef) XXX_Size() int {
return xxx_messageInfo_SpanRef.Size(m)
}
func (m *SpanRef) XXX_DiscardUnknown() {
xxx_messageInfo_SpanRef.DiscardUnknown(m)
}
var xxx_messageInfo_SpanRef proto.InternalMessageInfo
func (m *SpanRef) GetRefType() SpanRef_RefType {
if m != nil {
return m.RefType
}
return SpanRef_CHILD_OF
}
func (m *SpanRef) GetTraceId() uint64 {
if m != nil {
return m.TraceId
}
return 0
}
func (m *SpanRef) GetSpanId() uint64 {
if m != nil {
return m.SpanId
}
return 0
}
// Span represents a named unit of work performed by a service.
type Span struct {
Version int32 `protobuf:"varint,99,opt,name=version" json:"version,omitempty"`
ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName" json:"service_name,omitempty"`
OperationName string `protobuf:"bytes,2,opt,name=operation_name,json=operationName" json:"operation_name,omitempty"`
// Deprecated: caller no long required
Caller string `protobuf:"bytes,3,opt,name=caller" json:"caller,omitempty"`
TraceId uint64 `protobuf:"varint,4,opt,name=trace_id,json=traceId" json:"trace_id,omitempty"`
SpanId uint64 `protobuf:"varint,5,opt,name=span_id,json=spanId" json:"span_id,omitempty"`
ParentId uint64 `protobuf:"varint,6,opt,name=parent_id,json=parentId" json:"parent_id,omitempty"`
// Deprecated: level no long required
Level int32 `protobuf:"varint,7,opt,name=level" json:"level,omitempty"`
// Deprecated: use start_time instead instead of start_at
StartAt int64 `protobuf:"varint,8,opt,name=start_at,json=startAt" json:"start_at,omitempty"`
// Deprecated: use duration instead instead of finish_at
FinishAt int64 `protobuf:"varint,9,opt,name=finish_at,json=finishAt" json:"finish_at,omitempty"`
SamplingProbability float32 `protobuf:"fixed32,10,opt,name=sampling_probability,json=samplingProbability" json:"sampling_probability,omitempty"`
Env string `protobuf:"bytes,19,opt,name=env" json:"env,omitempty"`
StartTime *timestamp.Timestamp `protobuf:"bytes,20,opt,name=start_time,json=startTime" json:"start_time,omitempty"`
Duration *duration.Duration `protobuf:"bytes,21,opt,name=duration" json:"duration,omitempty"`
References []*SpanRef `protobuf:"bytes,22,rep,name=references" json:"references,omitempty"`
Tags []*Tag `protobuf:"bytes,11,rep,name=tags" json:"tags,omitempty"`
Logs []*Log `protobuf:"bytes,12,rep,name=logs" json:"logs,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Span) Reset() { *m = Span{} }
func (m *Span) String() string { return proto.CompactTextString(m) }
func (*Span) ProtoMessage() {}
func (*Span) Descriptor() ([]byte, []int) {
return fileDescriptor_span_68a8dae26ef502a2, []int{4}
}
func (m *Span) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Span.Unmarshal(m, b)
}
func (m *Span) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Span.Marshal(b, m, deterministic)
}
func (dst *Span) XXX_Merge(src proto.Message) {
xxx_messageInfo_Span.Merge(dst, src)
}
func (m *Span) XXX_Size() int {
return xxx_messageInfo_Span.Size(m)
}
func (m *Span) XXX_DiscardUnknown() {
xxx_messageInfo_Span.DiscardUnknown(m)
}
var xxx_messageInfo_Span proto.InternalMessageInfo
func (m *Span) GetVersion() int32 {
if m != nil {
return m.Version
}
return 0
}
func (m *Span) GetServiceName() string {
if m != nil {
return m.ServiceName
}
return ""
}
func (m *Span) GetOperationName() string {
if m != nil {
return m.OperationName
}
return ""
}
func (m *Span) GetCaller() string {
if m != nil {
return m.Caller
}
return ""
}
func (m *Span) GetTraceId() uint64 {
if m != nil {
return m.TraceId
}
return 0
}
func (m *Span) GetSpanId() uint64 {
if m != nil {
return m.SpanId
}
return 0
}
func (m *Span) GetParentId() uint64 {
if m != nil {
return m.ParentId
}
return 0
}
func (m *Span) GetLevel() int32 {
if m != nil {
return m.Level
}
return 0
}
func (m *Span) GetStartAt() int64 {
if m != nil {
return m.StartAt
}
return 0
}
func (m *Span) GetFinishAt() int64 {
if m != nil {
return m.FinishAt
}
return 0
}
func (m *Span) GetSamplingProbability() float32 {
if m != nil {
return m.SamplingProbability
}
return 0
}
func (m *Span) GetEnv() string {
if m != nil {
return m.Env
}
return ""
}
func (m *Span) GetStartTime() *timestamp.Timestamp {
if m != nil {
return m.StartTime
}
return nil
}
func (m *Span) GetDuration() *duration.Duration {
if m != nil {
return m.Duration
}
return nil
}
func (m *Span) GetReferences() []*SpanRef {
if m != nil {
return m.References
}
return nil
}
func (m *Span) GetTags() []*Tag {
if m != nil {
return m.Tags
}
return nil
}
func (m *Span) GetLogs() []*Log {
if m != nil {
return m.Logs
}
return nil
}
func init() {
proto.RegisterType((*Tag)(nil), "dapper.trace.Tag")
proto.RegisterType((*Field)(nil), "dapper.trace.Field")
proto.RegisterType((*Log)(nil), "dapper.trace.Log")
proto.RegisterType((*SpanRef)(nil), "dapper.trace.SpanRef")
proto.RegisterType((*Span)(nil), "dapper.trace.Span")
proto.RegisterEnum("dapper.trace.Tag_Kind", Tag_Kind_name, Tag_Kind_value)
proto.RegisterEnum("dapper.trace.Log_Kind", Log_Kind_name, Log_Kind_value)
proto.RegisterEnum("dapper.trace.SpanRef_RefType", SpanRef_RefType_name, SpanRef_RefType_value)
}
func init() { proto.RegisterFile("proto/span.proto", fileDescriptor_span_68a8dae26ef502a2) }
var fileDescriptor_span_68a8dae26ef502a2 = []byte{
// 669 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xdd, 0x6e, 0xd3, 0x4a,
0x10, 0xc7, 0xeb, 0xd8, 0x89, 0x9d, 0x49, 0x4e, 0xe5, 0xb3, 0xfd, 0x38, 0xdb, 0x1e, 0x3e, 0x4c,
0xa4, 0x4a, 0x06, 0x24, 0x07, 0x82, 0x2a, 0xc1, 0x65, 0x4b, 0x15, 0x88, 0x30, 0x0d, 0xda, 0x46,
0x42, 0xe2, 0x26, 0xda, 0x24, 0x63, 0x63, 0xd5, 0xb1, 0x2d, 0x7b, 0x1b, 0x29, 0xcf, 0xc0, 0x5b,
0xf0, 0x50, 0xdc, 0xf1, 0x2e, 0x68, 0xd7, 0x4e, 0x9a, 0xd2, 0x22, 0x04, 0x77, 0x3b, 0xf3, 0xff,
0xed, 0xce, 0xcc, 0xfa, 0xbf, 0x06, 0x3b, 0xcb, 0x53, 0x91, 0x76, 0x8b, 0x8c, 0x27, 0x9e, 0x5a,
0x92, 0xf6, 0x8c, 0x67, 0x19, 0xe6, 0x9e, 0xc8, 0xf9, 0x14, 0x0f, 0x1f, 0x86, 0x69, 0x1a, 0xc6,
0xd8, 0x55, 0xda, 0xe4, 0x2a, 0xe8, 0x8a, 0x68, 0x8e, 0x85, 0xe0, 0xf3, 0xac, 0xc4, 0x0f, 0x1f,
0xfc, 0x0c, 0xcc, 0xae, 0x72, 0x2e, 0xa2, 0xb4, 0x3a, 0xae, 0xf3, 0x45, 0x03, 0x7d, 0xc4, 0x43,
0x62, 0x83, 0x7e, 0x89, 0x4b, 0xaa, 0x39, 0x9a, 0xdb, 0x64, 0x72, 0x49, 0x9e, 0x80, 0x71, 0x19,
0x25, 0x33, 0x5a, 0x73, 0x34, 0x77, 0xbb, 0xb7, 0xef, 0x6d, 0xd6, 0xf5, 0x46, 0x3c, 0xf4, 0xde,
0x45, 0xc9, 0x8c, 0x29, 0x86, 0xec, 0x42, 0x7d, 0xc1, 0xe3, 0x2b, 0xa4, 0xba, 0xa3, 0xb9, 0x6d,
0x56, 0x06, 0x9d, 0x67, 0x60, 0x48, 0x86, 0x00, 0x34, 0x2e, 0x46, 0x6c, 0x70, 0xfe, 0xc6, 0xde,
0x22, 0x26, 0xe8, 0x83, 0xf3, 0x91, 0xad, 0x11, 0x0b, 0x8c, 0xd3, 0xe1, 0xd0, 0xb7, 0x6b, 0xa4,
0x09, 0xf5, 0xbe, 0x3f, 0x3c, 0x19, 0xd9, 0x7a, 0xa7, 0x0b, 0xf5, 0x7e, 0x84, 0xf1, 0xec, 0x8e,
0x76, 0xd6, 0x25, 0x6a, 0x9b, 0x25, 0xbe, 0x69, 0xa0, 0xfb, 0xe9, 0x1f, 0xb7, 0xef, 0xa7, 0xbf,
0x6f, 0x9f, 0xdc, 0x83, 0xe6, 0xfa, 0x36, 0xa9, 0xe1, 0x68, 0xae, 0xce, 0xae, 0x13, 0xe4, 0x29,
0x34, 0x02, 0xd9, 0x6a, 0x41, 0xeb, 0x8e, 0xee, 0xb6, 0x7a, 0x3b, 0x37, 0x2b, 0xa8, 0x31, 0x58,
0x85, 0xfc, 0xc5, 0x4d, 0x7c, 0xd5, 0xc0, 0xbc, 0xc8, 0x78, 0xc2, 0x30, 0x20, 0x2f, 0xc1, 0xca,
0x31, 0x18, 0x8b, 0x65, 0x86, 0x6a, 0xc2, 0xed, 0xde, 0xfd, 0x9b, 0xc5, 0x2a, 0xd0, 0x63, 0x18,
0x8c, 0x96, 0x19, 0x32, 0x33, 0x2f, 0x17, 0xe4, 0x00, 0x2c, 0x45, 0x8c, 0xa3, 0xf2, 0x22, 0x0c,
0x66, 0xaa, 0x78, 0x30, 0x23, 0xff, 0x81, 0x29, 0x5d, 0x25, 0x15, 0x5d, 0x29, 0x0d, 0x19, 0x0e,
0x66, 0x9d, 0xc7, 0x60, 0x56, 0xe7, 0x90, 0x36, 0x58, 0xaf, 0xdf, 0x0e, 0xfc, 0xb3, 0xf1, 0xb0,
0x6f, 0x6f, 0x11, 0x1b, 0xda, 0xfd, 0xa1, 0xef, 0x0f, 0x3f, 0x5e, 0x8c, 0xfb, 0x6c, 0xf8, 0xde,
0xd6, 0x3a, 0xdf, 0x0d, 0x30, 0x64, 0x6d, 0x42, 0xc1, 0x5c, 0x60, 0x5e, 0x44, 0x69, 0x42, 0xa7,
0x8e, 0xe6, 0xd6, 0xd9, 0x2a, 0x24, 0x8f, 0xa0, 0x5d, 0x60, 0xbe, 0x88, 0xa6, 0x38, 0x4e, 0xf8,
0x1c, 0xab, 0x2f, 0xd4, 0xaa, 0x72, 0xe7, 0x7c, 0x8e, 0xe4, 0x08, 0xb6, 0xd3, 0x0c, 0x4b, 0x57,
0x96, 0x50, 0x4d, 0x41, 0xff, 0xac, 0xb3, 0x0a, 0xdb, 0x87, 0xc6, 0x94, 0xc7, 0x31, 0xe6, 0xaa,
0xdf, 0x26, 0xab, 0xa2, 0x1b, 0x33, 0x1a, 0xbf, 0x9c, 0xb1, 0xbe, 0x39, 0x23, 0xf9, 0x1f, 0x9a,
0x19, 0xcf, 0x31, 0x11, 0x52, 0x6a, 0x28, 0xc9, 0x2a, 0x13, 0x03, 0xe5, 0x86, 0x18, 0x17, 0x18,
0x53, 0x53, 0x8d, 0x52, 0x06, 0xb2, 0x4c, 0x21, 0x78, 0x2e, 0xc6, 0x5c, 0x50, 0x4b, 0x99, 0xc1,
0x54, 0xf1, 0x89, 0x90, 0xa7, 0x05, 0x51, 0x12, 0x15, 0x9f, 0xa5, 0xd6, 0x54, 0x9a, 0x55, 0x26,
0x4e, 0x04, 0x79, 0x0e, 0xbb, 0x05, 0x9f, 0x67, 0x71, 0x94, 0x84, 0xe3, 0x2c, 0x4f, 0x27, 0x7c,
0x12, 0xc5, 0x91, 0x58, 0x52, 0x70, 0x34, 0xb7, 0xc6, 0x76, 0x56, 0xda, 0x87, 0x6b, 0x49, 0x9a,
0x19, 0x93, 0x05, 0xdd, 0x29, 0xcd, 0x8c, 0xc9, 0x82, 0xbc, 0x02, 0x28, 0x8b, 0x4b, 0xff, 0xd1,
0x5d, 0x47, 0x73, 0x5b, 0xbd, 0x43, 0xaf, 0x7c, 0xda, 0xde, 0xea, 0x69, 0x7b, 0xa3, 0x95, 0x39,
0x59, 0x53, 0xd1, 0x32, 0x26, 0xc7, 0x60, 0xad, 0x9e, 0x3c, 0xdd, 0x53, 0x1b, 0x0f, 0x6e, 0x6d,
0x3c, 0xab, 0x00, 0xb6, 0x46, 0xc9, 0x31, 0x40, 0x8e, 0x01, 0xe6, 0x98, 0x4c, 0xb1, 0xa0, 0xfb,
0xca, 0xe2, 0x7b, 0x77, 0xba, 0x8e, 0x6d, 0x80, 0xe4, 0x08, 0x0c, 0xc1, 0xc3, 0x82, 0xb6, 0xd4,
0x86, 0x7f, 0x6f, 0xfd, 0x34, 0x98, 0x92, 0x25, 0x16, 0xa7, 0x61, 0x41, 0xdb, 0x77, 0x61, 0x7e,
0x1a, 0x32, 0x25, 0x9f, 0xc2, 0x27, 0x4b, 0xf5, 0x18, 0x62, 0x32, 0x69, 0xa8, 0xd5, 0x8b, 0x1f,
0x01, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x7b, 0x57, 0x93, 0x12, 0x05, 0x00, 0x00,
}

View File

@@ -0,0 +1,77 @@
syntax = "proto3";
package dapper.trace;
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
option go_package = "protogen";
message Tag {
enum Kind {
STRING = 0;
INT = 1;
BOOL = 2;
FLOAT = 3;
}
string key = 1;
Kind kind = 2;
bytes value = 3;
}
message Field {
string key = 1;
bytes value = 2;
}
message Log {
// Deprecated: Kind no long use
enum Kind {
STRING = 0;
INT = 1;
BOOL = 2;
FLOAT = 3;
}
string key = 1;
// Deprecated: Kind no long use
Kind kind = 2;
// Deprecated: Value no long use
bytes value = 3;
int64 timestamp = 4;
repeated Field fields = 5;
}
// SpanRef describes causal relationship of the current span to another span (e.g. 'child-of')
message SpanRef {
enum RefType {
CHILD_OF = 0;
FOLLOWS_FROM = 1;
}
RefType ref_type = 1;
uint64 trace_id = 2;
uint64 span_id = 3;
}
// Span represents a named unit of work performed by a service.
message Span {
int32 version = 99;
string service_name = 1;
string operation_name = 2;
// Deprecated: caller no long required
string caller = 3;
uint64 trace_id = 4;
uint64 span_id = 5;
uint64 parent_id = 6;
// Deprecated: level no long required
int32 level = 7;
// Deprecated: use start_time instead instead of start_at
int64 start_at = 8;
// Deprecated: use duration instead instead of finish_at
int64 finish_at = 9;
float sampling_probability = 10;
string env = 19;
google.protobuf.Timestamp start_time = 20;
google.protobuf.Duration duration = 21;
repeated SpanRef references = 22;
repeated Tag tags = 11;
repeated Log logs = 12;
}

138
library/net/trace/report.go Normal file
View File

@@ -0,0 +1,138 @@
package trace
import (
"fmt"
"net"
"os"
"sync"
"time"
)
const (
// MaxPackageSize .
_maxPackageSize = 1024 * 32
// safe udp package size // MaxPackageSize = 508 _dataChSize = 4096)
// max memory usage 1024 * 32 * 4096 -> 128MB
_dataChSize = 4096
_defaultWriteChannalTimeout = 50 * time.Millisecond
_defaultWriteTimeout = 200 * time.Millisecond
)
// reporter trace reporter.
type reporter interface {
WriteSpan(sp *span) error
Close() error
}
// newReport with network address
func newReport(network, address string, timeout time.Duration, protocolVersion int32) reporter {
if timeout == 0 {
timeout = _defaultWriteTimeout
}
report := &connReport{
network: network,
address: address,
dataCh: make(chan []byte, _dataChSize),
done: make(chan struct{}),
timeout: timeout,
version: protocolVersion,
}
go report.daemon()
return report
}
type connReport struct {
version int32
rmx sync.RWMutex
closed bool
network, address string
dataCh chan []byte
conn net.Conn
done chan struct{}
timeout time.Duration
}
func (c *connReport) daemon() {
for b := range c.dataCh {
c.send(b)
}
c.done <- struct{}{}
}
func (c *connReport) WriteSpan(sp *span) error {
data, err := marshalSpan(sp, c.version)
if err != nil {
return err
}
return c.writePackage(data)
}
func (c *connReport) writePackage(data []byte) error {
c.rmx.RLock()
defer c.rmx.RUnlock()
if c.closed {
return fmt.Errorf("report already closed")
}
if len(data) > _maxPackageSize {
return fmt.Errorf("package too large length %d > %d", len(data), _maxPackageSize)
}
select {
case c.dataCh <- data:
return nil
case <-time.After(_defaultWriteChannalTimeout):
return fmt.Errorf("write to data channel timeout")
}
}
func (c *connReport) Close() error {
c.rmx.Lock()
c.closed = true
c.rmx.Unlock()
t := time.NewTimer(time.Second)
close(c.dataCh)
select {
case <-t.C:
c.closeConn()
return fmt.Errorf("close report timeout force close")
case <-c.done:
return c.closeConn()
}
}
func (c *connReport) send(data []byte) {
if c.conn == nil {
if err := c.reconnect(); err != nil {
c.Errorf("connect error: %s retry after second", err)
time.Sleep(time.Second)
return
}
}
c.conn.SetWriteDeadline(time.Now().Add(100 * time.Microsecond))
if _, err := c.conn.Write(data); err != nil {
c.Errorf("write to conn error: %s, close connect", err)
c.conn.Close()
c.conn = nil
}
}
func (c *connReport) reconnect() (err error) {
c.conn, err = net.DialTimeout(c.network, c.address, c.timeout)
return
}
func (c *connReport) closeConn() error {
if c.conn != nil {
return c.conn.Close()
}
return nil
}
func (c *connReport) Errorf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
}

View File

@@ -0,0 +1,88 @@
package trace
import (
"bytes"
"io"
"log"
"net"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func newServer(w io.Writer, network, address string) (func() error, error) {
lis, err := net.Listen(network, address)
if err != nil {
return nil, err
}
done := make(chan struct{})
go func() {
conn, err := lis.Accept()
if err != nil {
lis.Close()
log.Fatal(err)
}
io.Copy(w, conn)
conn.Close()
done <- struct{}{}
}()
return func() error {
<-done
return lis.Close()
}, nil
}
func TestReportTCP(t *testing.T) {
buf := &bytes.Buffer{}
cancel, err := newServer(buf, "tcp", "127.0.0.1:6077")
if err != nil {
t.Fatal(err)
}
report := newReport("tcp", "127.0.0.1:6077", 0, 0).(*connReport)
data := []byte("hello, world")
report.writePackage(data)
if err := report.Close(); err != nil {
t.Error(err)
}
cancel()
assert.Equal(t, data, buf.Bytes(), "receive data")
}
func newUnixgramServer(w io.Writer, address string) (func() error, error) {
conn, err := net.ListenPacket("unixgram", address)
if err != nil {
return nil, err
}
done := make(chan struct{})
go func() {
p := make([]byte, 4096)
n, _, err := conn.ReadFrom(p)
if err != nil {
log.Fatal(err)
}
w.Write(p[:n])
done <- struct{}{}
}()
return func() error {
<-done
return conn.Close()
}, nil
}
func TestReportUnixgram(t *testing.T) {
os.Remove("/tmp/trace.sock")
buf := &bytes.Buffer{}
cancel, err := newUnixgramServer(buf, "/tmp/trace.sock")
if err != nil {
t.Fatal(err)
}
report := newReport("unixgram", "/tmp/trace.sock", 0, 0).(*connReport)
data := []byte("hello, world")
report.writePackage(data)
if err := report.Close(); err != nil {
t.Error(err)
}
cancel()
assert.Equal(t, data, buf.Bytes(), "receive data")
}

View File

@@ -0,0 +1,67 @@
package trace
import (
"math/rand"
"sync/atomic"
"time"
)
const (
slotLength = 2048
)
var ignoreds = []string{"/metrics", "/monitor/ping"}
func init() {
rand.Seed(time.Now().UnixNano())
}
func oneAtTimeHash(s string) (hash uint32) {
b := []byte(s)
for i := range b {
hash += uint32(b[i])
hash += hash << 10
hash ^= hash >> 6
}
hash += hash << 3
hash ^= hash >> 11
hash += hash << 15
return
}
// sampler decides whether a new trace should be sampled or not.
type sampler interface {
IsSampled(traceID uint64, operationName string) (bool, float32)
Close() error
}
type probabilitySampling struct {
probability float32
slot [slotLength]int64
}
func (p *probabilitySampling) IsSampled(traceID uint64, operationName string) (bool, float32) {
for _, ignored := range ignoreds {
if operationName == ignored {
return false, 0
}
}
now := time.Now().Unix()
idx := oneAtTimeHash(operationName) % slotLength
old := atomic.LoadInt64(&p.slot[idx])
if old != now {
atomic.SwapInt64(&p.slot[idx], now)
return true, 1
}
return rand.Float32() < float32(p.probability), float32(p.probability)
}
func (p *probabilitySampling) Close() error { return nil }
// newSampler new probability sampler
func newSampler(probability float32) sampler {
if probability <= 0 || probability > 1 {
panic("probability P ∈ (0, 1]")
}
return &probabilitySampling{probability: probability}
}

View File

@@ -0,0 +1,35 @@
package trace
import (
"testing"
)
func TestProbabilitySampling(t *testing.T) {
sampler := newSampler(0.001)
t.Run("test one operationName", func(t *testing.T) {
sampled, probability := sampler.IsSampled(0, "test123")
if !sampled || probability != 1 {
t.Errorf("expect sampled and probability == 1 get: %v %f", sampled, probability)
}
})
t.Run("test probability", func(t *testing.T) {
sampler.IsSampled(0, "test_opt_2")
count := 0
for i := 0; i < 100000; i++ {
sampled, _ := sampler.IsSampled(0, "test_opt_2")
if sampled {
count++
}
}
if count < 80 || count > 120 {
t.Errorf("expect count between 80~120 get %d", count)
}
})
}
func BenchmarkProbabilitySampling(b *testing.B) {
sampler := newSampler(0.001)
for i := 0; i < b.N; i++ {
sampler.IsSampled(0, "test_opt_xxx")
}
}

116
library/net/trace/span.go Normal file
View File

@@ -0,0 +1,116 @@
package trace
import (
"fmt"
"strconv"
"time"
protogen "go-common/library/net/trace/proto"
)
const (
_maxChilds = 1024
_maxTags = 128
_maxLogs = 256
)
var _ Trace = &span{}
type span struct {
dapper *dapper
context spanContext
operationName string
startTime time.Time
duration time.Duration
tags []Tag
logs []*protogen.Log
childs int
}
func (s *span) Fork(serviceName, operationName string) Trace {
if s.childs > _maxChilds {
// if child span more than max childs set return noopspan
return noopspan{}
}
s.childs++
// 为了兼容临时为 New 的 Span 设置 span.kind
return s.dapper.newSpanWithContext(operationName, s.context).SetTag(TagString(TagSpanKind, "client"))
}
func (s *span) Follow(serviceName, operationName string) Trace {
return s.Fork(serviceName, operationName).SetTag(TagString(TagSpanKind, "producer"))
}
func (s *span) Finish(perr *error) {
s.duration = time.Since(s.startTime)
if perr != nil && *perr != nil {
err := *perr
s.SetTag(TagBool(TagError, true))
s.SetLog(Log(LogMessage, err.Error()))
if err, ok := err.(stackTracer); ok {
s.SetLog(Log(LogStack, fmt.Sprintf("%+v", err.StackTrace())))
}
}
s.dapper.report(s)
}
func (s *span) SetTag(tags ...Tag) Trace {
if !s.context.isSampled() && !s.context.isDebug() {
return s
}
if len(s.tags) < _maxTags {
s.tags = append(s.tags, tags...)
}
if len(s.tags) == _maxTags {
s.tags = append(s.tags, Tag{Key: "trace.error", Value: "too many tags"})
}
return s
}
// LogFields is an efficient and type-checked way to record key:value
// NOTE current unsupport
func (s *span) SetLog(logs ...LogField) Trace {
if !s.context.isSampled() && !s.context.isDebug() {
return s
}
if len(s.logs) < _maxLogs {
s.setLog(logs...)
}
if len(s.logs) == _maxLogs {
s.setLog(LogField{Key: "trace.error", Value: "too many logs"})
}
return s
}
func (s *span) setLog(logs ...LogField) Trace {
protoLog := &protogen.Log{
Timestamp: time.Now().UnixNano(),
Fields: make([]*protogen.Field, len(logs)),
}
for i := range logs {
protoLog.Fields[i] = &protogen.Field{Key: logs[i].Key, Value: []byte(logs[i].Value)}
}
s.logs = append(s.logs, protoLog)
return s
}
// Visit visits the k-v pair in trace, calling fn for each.
func (s *span) Visit(fn func(k, v string)) {
// NOTE: Deprecated key: delete in future
fn(KeyTraceID, strconv.FormatUint(s.context.traceID, 10))
fn(KeyTraceSpanID, strconv.FormatUint(s.context.spanID, 10))
fn(KeyTraceParentID, strconv.FormatUint(s.context.parentID, 10))
fn(KeyTraceSampled, strconv.FormatBool(s.context.isSampled()))
fn(KeyTraceCaller, s.dapper.serviceName)
fn(BiliTraceID, s.context.String())
}
// SetTitle reset trace title
func (s *span) SetTitle(operationName string) {
s.operationName = operationName
}
func (s *span) String() string {
return s.context.String()
}

View File

@@ -0,0 +1,108 @@
package trace
import (
"fmt"
"strconv"
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestSpan(t *testing.T) {
report := &mockReport{}
t1 := newTracer("service1", report, &Config{DisableSample: true})
t.Run("test span string", func(t *testing.T) {
sp1 := t1.New("testfinish").(*span)
assert.NotEmpty(t, fmt.Sprint(sp1))
})
t.Run("test fork", func(t *testing.T) {
sp1 := t1.New("testfork").(*span)
sp2 := sp1.Fork("xxx", "opt_2").(*span)
assert.Equal(t, sp1.context.traceID, sp2.context.traceID)
assert.Equal(t, sp1.context.spanID, sp2.context.parentID)
t.Run("test max fork", func(t *testing.T) {
sp3 := sp2.Fork("xx", "xxx")
for i := 0; i < 100; i++ {
sp3 = sp3.Fork("", "xxx")
}
assert.Equal(t, noopspan{}, sp3)
})
t.Run("test max childs", func(t *testing.T) {
sp3 := sp2.Fork("xx", "xxx")
for i := 0; i < 4096; i++ {
sp3.Fork("", "xxx")
}
assert.Equal(t, noopspan{}, sp3.Fork("xx", "xx"))
})
})
t.Run("test finish", func(t *testing.T) {
t.Run("test finish ok", func(t *testing.T) {
sp1 := t1.New("testfinish").(*span)
time.Sleep(time.Millisecond)
sp1.Finish(nil)
assert.True(t, sp1.startTime.Unix() > 0)
assert.True(t, sp1.duration > time.Microsecond)
})
t.Run("test finish error", func(t *testing.T) {
sp1 := t1.New("testfinish").(*span)
time.Sleep(time.Millisecond)
err := fmt.Errorf("🍻")
sp1.Finish(&err)
assert.True(t, sp1.startTime.Unix() > 0)
assert.True(t, sp1.duration > time.Microsecond)
errorTag := false
for _, tag := range sp1.tags {
if tag.Key == TagError && tag.Value != nil {
errorTag = true
}
}
assert.True(t, errorTag)
messageLog := false
for _, log := range sp1.logs {
assert.True(t, log.Timestamp != 0)
for _, field := range log.Fields {
if field.Key == LogMessage && len(field.Value) != 0 {
messageLog = true
}
}
}
assert.True(t, messageLog)
})
t.Run("test finish error stack", func(t *testing.T) {
sp1 := t1.New("testfinish").(*span)
time.Sleep(time.Millisecond)
err := fmt.Errorf("🍻")
err = errors.WithStack(err)
sp1.Finish(&err)
ok := false
for _, log := range sp1.logs {
for _, field := range log.Fields {
if field.Key == LogStack && len(field.Value) != 0 {
ok = true
}
}
}
assert.True(t, ok, "LogStack set")
})
t.Run("test too many tags", func(t *testing.T) {
sp1 := t1.New("testfinish").(*span)
for i := 0; i < 1024; i++ {
sp1.SetTag(Tag{Key: strconv.Itoa(i), Value: "hello"})
}
assert.Len(t, sp1.tags, _maxTags+1)
assert.Equal(t, sp1.tags[_maxTags].Key, "trace.error")
assert.Equal(t, sp1.tags[_maxTags].Value, "too many tags")
})
t.Run("test too many logs", func(t *testing.T) {
sp1 := t1.New("testfinish").(*span)
for i := 0; i < 1024; i++ {
sp1.SetLog(LogField{Key: strconv.Itoa(i), Value: "hello"})
}
assert.Len(t, sp1.logs, _maxLogs+1)
assert.Equal(t, sp1.logs[_maxLogs].Fields[0].Key, "trace.error")
assert.Equal(t, sp1.logs[_maxLogs].Fields[0].Value, []byte("too many logs"))
})
})
}

182
library/net/trace/tag.go Normal file
View File

@@ -0,0 +1,182 @@
package trace
// Standard Span tags https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table
const (
// The software package, framework, library, or module that generated the associated Span.
// E.g., "grpc", "django", "JDBI".
// type string
TagComponent = "component"
// Database instance name.
// E.g., In java, if the jdbc.url="jdbc:mysql://127.0.0.1:3306/customers", the instance name is "customers".
// type string
TagDBInstance = "db.instance"
// A database statement for the given database type.
// E.g., for db.type="sql", "SELECT * FROM wuser_table"; for db.type="redis", "SET mykey 'WuValue'".
TagDBStatement = "db.statement"
// Database type. For any SQL database, "sql". For others, the lower-case database category,
// e.g. "cassandra", "hbase", or "redis".
// type string
TagDBType = "db.type"
// Username for accessing database. E.g., "readonly_user" or "reporting_user"
// type string
TagDBUser = "db.user"
// true if and only if the application considers the operation represented by the Span to have failed
// type bool
TagError = "error"
// HTTP method of the request for the associated Span. E.g., "GET", "POST"
// type string
TagHTTPMethod = "http.method"
// HTTP response status code for the associated Span. E.g., 200, 503, 404
// type integer
TagHTTPStatusCode = "http.status_code"
// URL of the request being handled in this segment of the trace, in standard URI format.
// E.g., "https://domain.net/path/to?resource=here"
// type string
TagHTTPURL = "http.url"
// An address at which messages can be exchanged.
// E.g. A Kafka record has an associated "topic name" that can be extracted by the instrumented producer or consumer and stored using this tag.
// type string
TagMessageBusDestination = "message_bus.destination"
// Remote "address", suitable for use in a networking client library.
// This may be a "ip:port", a bare "hostname", a FQDN, or even a JDBC substring like "mysql://prod-db:3306"
// type string
TagPeerAddress = "peer.address"
// Remote hostname. E.g., "opentracing.io", "internal.dns.name"
// type string
TagPeerHostname = "peer.hostname"
// Remote IPv4 address as a .-separated tuple. E.g., "127.0.0.1"
// type string
TagPeerIPv4 = "peer.ipv4"
// Remote IPv6 address as a string of colon-separated 4-char hex tuples.
// E.g., "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
// type string
TagPeerIPv6 = "peer.ipv6"
// Remote port. E.g., 80
// type integer
TagPeerPort = "peer.port"
// Remote service name (for some unspecified definition of "service").
// E.g., "elasticsearch", "a_custom_microservice", "memcache"
// type string
TagPeerService = "peer.service"
// If greater than 0, a hint to the Tracer to do its best to capture the trace.
// If 0, a hint to the trace to not-capture the trace. If absent, the Tracer should use its default sampling mechanism.
// type string
TagSamplingPriority = "sampling.priority"
// Either "client" or "server" for the appropriate roles in an RPC,
// and "producer" or "consumer" for the appropriate roles in a messaging scenario.
// type string
TagSpanKind = "span.kind"
// legacy tag
TagAnnotation = "legacy.annotation"
TagAddress = "legacy.address"
TagComment = "legacy.comment"
)
// Standard log tags
const (
// The type or "kind" of an error (only for event="error" logs). E.g., "Exception", "OSError"
// type string
LogErrorKind = "error.kind"
// For languages that support such a thing (e.g., Java, Python),
// the actual Throwable/Exception/Error object instance itself.
// E.g., A java.lang.UnsupportedOperationException instance, a python exceptions.NameError instance
// type string
LogErrorObject = "error.object"
// A stable identifier for some notable moment in the lifetime of a Span. For instance, a mutex lock acquisition or release or the sorts of lifetime events in a browser page load described in the Performance.timing specification. E.g., from Zipkin, "cs", "sr", "ss", or "cr". Or, more generally, "initialized" or "timed out". For errors, "error"
// type string
LogEvent = "event"
// A concise, human-readable, one-line message explaining the event.
// E.g., "Could not connect to backend", "Cache invalidation succeeded"
// type string
LogMessage = "message"
// A stack trace in platform-conventional format; may or may not pertain to an error. E.g., "File \"example.py\", line 7, in \<module\>\ncaller()\nFile \"example.py\", line 5, in caller\ncallee()\nFile \"example.py\", line 2, in callee\nraise Exception(\"Yikes\")\n"
// type string
LogStack = "stack"
)
// Tag interface
type Tag struct {
Key string
Value interface{}
}
// TagString new string tag.
func TagString(key string, val string) Tag {
return Tag{Key: key, Value: val}
}
// TagInt64 new int64 tag.
func TagInt64(key string, val int64) Tag {
return Tag{Key: key, Value: val}
}
// TagInt new int tag
func TagInt(key string, val int) Tag {
return Tag{Key: key, Value: val}
}
// TagBool new bool tag
func TagBool(key string, val bool) Tag {
return Tag{Key: key, Value: val}
}
// TagFloat64 new float64 tag
func TagFloat64(key string, val float64) Tag {
return Tag{Key: key, Value: val}
}
// TagFloat32 new float64 tag
func TagFloat32(key string, val float32) Tag {
return Tag{Key: key, Value: val}
}
// String new tag String.
// NOTE: use TagString
func String(key string, val string) Tag {
return TagString(key, val)
}
// Int new tag Int.
// NOTE: use TagInt
func Int(key string, val int) Tag {
return TagInt(key, val)
}
// Bool new tagBool
// NOTE: use TagBool
func Bool(key string, val bool) Tag {
return TagBool(key, val)
}
// Log new log.
func Log(key string, val string) LogField {
return LogField{Key: key, Value: val}
}
// LogField LogField
type LogField struct {
Key string
Value string
}

View File

@@ -0,0 +1 @@
package trace

View File

@@ -0,0 +1,92 @@
package trace
import (
"io"
)
var (
// global tracer
_tracer Tracer = nooptracer{}
)
// SetGlobalTracer SetGlobalTracer
func SetGlobalTracer(tracer Tracer) {
_tracer = tracer
}
// Tracer is a simple, thin interface for Trace creation and propagation.
type Tracer interface {
// New trace instance with given title.
New(operationName string, opts ...Option) Trace
// Inject takes the Trace instance and injects it for
// propagation within `carrier`. The actual type of `carrier` depends on
// the value of `format`.
Inject(t Trace, format interface{}, carrier interface{}) error
// Extract returns a Trace instance given `format` and `carrier`.
// return `ErrTraceNotFound` if trace not found.
Extract(format interface{}, carrier interface{}) (Trace, error)
}
// New trace instance with given operationName.
func New(operationName string, opts ...Option) Trace {
return _tracer.New(operationName, opts...)
}
// Inject takes the Trace instance and injects it for
// propagation within `carrier`. The actual type of `carrier` depends on
// the value of `format`.
func Inject(t Trace, format interface{}, carrier interface{}) error {
return _tracer.Inject(t, format, carrier)
}
// Extract returns a Trace instance given `format` and `carrier`.
// return `ErrTraceNotFound` if trace not found.
func Extract(format interface{}, carrier interface{}) (Trace, error) {
return _tracer.Extract(format, carrier)
}
// Close trace flush data.
func Close() error {
if closer, ok := _tracer.(io.Closer); ok {
return closer.Close()
}
return nil
}
// Trace trace common interface.
type Trace interface {
// Fork fork a trace with client trace.
Fork(serviceName, operationName string) Trace
// Follow
Follow(serviceName, operationName string) Trace
// Finish when trace finish call it.
Finish(err *error)
// Scan scan trace into info.
// Deprecated: method Scan is deprecated, use Inject instead of Scan
// Scan(ti *Info)
// Adds a tag to the trace.
//
// If there is a pre-existing tag set for `key`, it is overwritten.
//
// Tag values can be numeric types, strings, or bools. The behavior of
// other tag value types is undefined at the OpenTracing level. If a
// tracing system does not know how to handle a particular value type, it
// may ignore the tag, but shall not panic.
// NOTE current only support legacy tag: TagAnnotation TagAddress TagComment
// other will be ignore
SetTag(tags ...Tag) Trace
// LogFields is an efficient and type-checked way to record key:value
// NOTE current unsupport
SetLog(logs ...LogField) Trace
// Visit visits the k-v pair in trace, calling fn for each.
Visit(fn func(k, v string))
// SetTitle reset trace title
SetTitle(title string)
}

75
library/net/trace/util.go Normal file
View File

@@ -0,0 +1,75 @@
package trace
import (
"context"
"encoding/binary"
"math/rand"
"time"
"github.com/pkg/errors"
"go-common/library/conf/env"
"go-common/library/net/metadata"
)
var _hostHash byte
func init() {
rand.Seed(time.Now().UnixNano())
_hostHash = byte(oneAtTimeHash(env.Hostname))
}
func extendTag() (tags []Tag) {
tags = append(tags,
TagString("hostname", env.Hostname),
TagString("ip", env.IP),
TagString("zone", env.Zone),
TagString("region", env.Region),
)
return
}
func serviceNameFromEnv() string {
return env.AppID
}
func isUATEnv() bool {
return env.DeployEnv == env.DeployEnvUat
}
func genID() uint64 {
var b [8]byte
// i think this code will not survive to 2106-02-07
binary.BigEndian.PutUint32(b[4:], uint32(time.Now().Unix())>>8)
b[4] = _hostHash
binary.BigEndian.PutUint32(b[:4], uint32(rand.Int31()))
return binary.BigEndian.Uint64(b[:])
}
type stackTracer interface {
StackTrace() errors.StackTrace
}
type ctxKey string
var _ctxkey ctxKey = "go-common/net/trace.trace"
// FromContext returns the trace bound to the context, if any.
func FromContext(ctx context.Context) (t Trace, ok bool) {
if v := metadata.Value(ctx, metadata.Trace); v != nil {
t, ok = v.(Trace)
return
}
t, ok = ctx.Value(_ctxkey).(Trace)
return
}
// NewContext new a trace context.
// NOTE: This method is not thread safe.
func NewContext(ctx context.Context, t Trace) context.Context {
if md, ok := metadata.FromContext(ctx); ok {
md[metadata.Trace] = t
return ctx
}
return context.WithValue(ctx, _ctxkey, t)
}

View File

@@ -0,0 +1,21 @@
package trace
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFromContext(t *testing.T) {
report := &mockReport{}
t1 := newTracer("service1", report, &Config{DisableSample: true})
sp1 := t1.New("test123")
ctx := context.Background()
ctx = NewContext(ctx, sp1)
sp2, ok := FromContext(ctx)
if !ok {
t.Fatal("nothing from context")
}
assert.Equal(t, sp1, sp2)
}