Create & Init Project...
This commit is contained in:
63
app/service/main/dapper/collector/BUILD
Normal file
63
app/service/main/dapper/collector/BUILD
Normal file
@ -0,0 +1,63 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"breaker.go",
|
||||
"collector.go",
|
||||
"detect.go",
|
||||
"operation_name.go",
|
||||
"process.go",
|
||||
],
|
||||
importpath = "go-common/app/service/main/dapper/collector",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/main/dapper/conf:go_default_library",
|
||||
"//app/service/main/dapper/dao:go_default_library",
|
||||
"//app/service/main/dapper/model:go_default_library",
|
||||
"//app/service/main/dapper/pkg/batchwrite:go_default_library",
|
||||
"//app/service/main/dapper/pkg/collect:go_default_library",
|
||||
"//app/service/main/dapper/pkg/collect/kafkacollect:go_default_library",
|
||||
"//app/service/main/dapper/pkg/collect/tcpcollect:go_default_library",
|
||||
"//app/service/main/dapper/pkg/pointwrite:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"breaker_test.go",
|
||||
"collector_test.go",
|
||||
"detect_test.go",
|
||||
"operation_name_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//app/service/main/dapper/model:go_default_library",
|
||||
"//library/net/trace/proto:go_default_library",
|
||||
],
|
||||
)
|
69
app/service/main/dapper/collector/breaker.go
Normal file
69
app/service/main/dapper/collector/breaker.go
Normal file
@ -0,0 +1,69 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go-common/app/service/main/dapper/model"
|
||||
)
|
||||
|
||||
type countBreaker struct {
|
||||
rmx sync.RWMutex
|
||||
n int
|
||||
slot map[string]struct{}
|
||||
}
|
||||
|
||||
func (c *countBreaker) Break(key string) error {
|
||||
c.rmx.Lock()
|
||||
_, ok := c.slot[key]
|
||||
c.rmx.Unlock()
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
c.rmx.Lock()
|
||||
c.slot[key] = struct{}{}
|
||||
l := len(c.slot)
|
||||
c.rmx.Unlock()
|
||||
if l <= c.n {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%s reach limit number %d breaked", key, c.n)
|
||||
}
|
||||
|
||||
func newCountBreaker(n int) *countBreaker {
|
||||
return &countBreaker{n: n, slot: make(map[string]struct{})}
|
||||
}
|
||||
|
||||
type serviceBreaker struct {
|
||||
rmx sync.RWMutex
|
||||
n int
|
||||
slot map[string]*countBreaker
|
||||
}
|
||||
|
||||
func (s *serviceBreaker) Process(span *model.Span) error {
|
||||
s.rmx.RLock()
|
||||
operationNameBreaker, ok1 := s.slot[span.ServiceName+"_o"]
|
||||
peerServiceBreaker, ok2 := s.slot[span.ServiceName+"_p"]
|
||||
s.rmx.RUnlock()
|
||||
if !ok1 || !ok2 {
|
||||
s.rmx.Lock()
|
||||
if !ok1 {
|
||||
operationNameBreaker = newCountBreaker(s.n)
|
||||
s.slot[span.ServiceName+"_o"] = operationNameBreaker
|
||||
}
|
||||
if !ok2 {
|
||||
peerServiceBreaker = newCountBreaker(s.n)
|
||||
s.slot[span.ServiceName+"_p"] = peerServiceBreaker
|
||||
}
|
||||
s.rmx.Unlock()
|
||||
}
|
||||
if err := operationNameBreaker.Break(span.OperationName); err != nil {
|
||||
return err
|
||||
}
|
||||
return peerServiceBreaker.Break(span.StringTag("peer.service"))
|
||||
}
|
||||
|
||||
// NewServiceBreakerProcess .
|
||||
func NewServiceBreakerProcess(n int) Processer {
|
||||
return &serviceBreaker{n: n, slot: make(map[string]*countBreaker)}
|
||||
}
|
24
app/service/main/dapper/collector/breaker_test.go
Normal file
24
app/service/main/dapper/collector/breaker_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"go-common/app/service/main/dapper/model"
|
||||
)
|
||||
|
||||
func TestServiceBreaker(t *testing.T) {
|
||||
breaker := NewServiceBreakerProcess(10)
|
||||
for i := 0; i < 20; i++ {
|
||||
err := breaker.Process(&model.Span{ServiceName: "test", OperationName: fmt.Sprintf("opt_%d", i)})
|
||||
if i < 10 {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Error("expect breaked")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
195
app/service/main/dapper/collector/collector.go
Normal file
195
app/service/main/dapper/collector/collector.go
Normal file
@ -0,0 +1,195 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go-common/app/service/main/dapper/conf"
|
||||
"go-common/app/service/main/dapper/dao"
|
||||
"go-common/app/service/main/dapper/model"
|
||||
"go-common/app/service/main/dapper/pkg/batchwrite"
|
||||
"go-common/app/service/main/dapper/pkg/collect"
|
||||
"go-common/app/service/main/dapper/pkg/collect/kafkacollect"
|
||||
"go-common/app/service/main/dapper/pkg/collect/tcpcollect"
|
||||
"go-common/app/service/main/dapper/pkg/pointwrite"
|
||||
"go-common/library/log"
|
||||
bm "go-common/library/net/http/blademaster"
|
||||
)
|
||||
|
||||
// Collector dapper collector receving trace data from tcp and write
|
||||
// to hbase and influxdb
|
||||
type Collector struct {
|
||||
daoImpl dao.Dao
|
||||
cfg *conf.Config
|
||||
bw batchwrite.BatchWriter
|
||||
pw pointwrite.PointWriter
|
||||
process []Processer
|
||||
clts []collect.Collecter
|
||||
tcpClt *tcpcollect.TCPCollect
|
||||
}
|
||||
|
||||
func (c *Collector) checkRetention(span *model.Span) error {
|
||||
if c.cfg.Dapper.RetentionDay != 0 {
|
||||
retentionSecond := c.cfg.Dapper.RetentionDay * 3600 * 24
|
||||
if (time.Now().Unix() - span.StartTime.Unix()) > int64(retentionSecond) {
|
||||
return fmt.Errorf("span beyond retention policy ignored")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process dispatch protoSpan
|
||||
func (c *Collector) Process(ctx context.Context, protoSpan *model.ProtoSpan) error {
|
||||
log.V(5).Info("dispatch span form serviceName: %s, operationName: %s", protoSpan.ServiceName, protoSpan.OperationName)
|
||||
// ignored serviceName empry span
|
||||
if protoSpan.ServiceName == "" {
|
||||
log.Warn("span miss servicename ignored")
|
||||
return nil
|
||||
}
|
||||
|
||||
// fix operationName
|
||||
if protoSpan.OperationName == "" {
|
||||
protoSpan.OperationName = "missing operationname !!"
|
||||
}
|
||||
if protoSpan.ServiceName == "" {
|
||||
log.Warn("span miss servicename ignored")
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert to model.Span
|
||||
span, err := model.FromProtoSpan(protoSpan, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// run process
|
||||
for _, p := range c.process {
|
||||
if err := p.Process(span); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.writeSamplePoint(span); err != nil {
|
||||
log.Error("write sample point error: %s", err)
|
||||
}
|
||||
if c.writeRawTrace(span); err != nil {
|
||||
log.Error("write sample point error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) writeSamplePoint(span *model.Span) error {
|
||||
return c.pw.WriteSpan(span)
|
||||
}
|
||||
|
||||
func (c *Collector) writeRawTrace(span *model.Span) error {
|
||||
return c.bw.WriteSpan(span)
|
||||
}
|
||||
|
||||
// ListenAndStart listen and start collector server
|
||||
func (c *Collector) ListenAndStart() error {
|
||||
tcpClt := tcpcollect.New(c.cfg.Collect)
|
||||
// NOTE: remove this future
|
||||
c.tcpClt = tcpClt
|
||||
c.clts = append(c.clts, tcpClt)
|
||||
// if KafkaCollect is configured enable kafka collect
|
||||
if c.cfg.KafkaCollect != nil {
|
||||
kafkaClt, err := kafkacollect.New(c.cfg.KafkaCollect.Topic, c.cfg.KafkaCollect.Addrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.clts = append(c.clts, kafkaClt)
|
||||
}
|
||||
for _, clt := range c.clts {
|
||||
clt.RegisterProcess(c)
|
||||
if err := clt.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.cfg.Dapper.APIListen != "" {
|
||||
c.listenAndStartAPI()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) listenAndStartAPI() {
|
||||
engine := bm.NewServer(nil)
|
||||
engine.GET("/x/internal/dapper-collector/client-status", c.clientStatusHandler)
|
||||
engine.Start()
|
||||
}
|
||||
|
||||
// Close collector
|
||||
func (c *Collector) Close() error {
|
||||
for _, clt := range c.clts {
|
||||
if err := clt.Close(); err != nil {
|
||||
log.Error("close collect error: %s", err)
|
||||
}
|
||||
}
|
||||
c.bw.Close()
|
||||
c.pw.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientStatus .
|
||||
func (c *Collector) clientStatusHandler(bc *bm.Context) {
|
||||
resp := &model.ClientStatusResp{QueueLen: c.bw.QueueLen()}
|
||||
for _, cs := range c.tcpClt.ClientStatus() {
|
||||
resp.Clients = append(resp.Clients, &model.ClientStatus{
|
||||
Addr: cs.Addr,
|
||||
UpTime: cs.UpTime,
|
||||
ErrCount: cs.ErrorCounter.Value(),
|
||||
Rate: cs.Counter.Value(),
|
||||
})
|
||||
}
|
||||
bc.JSON(resp, nil)
|
||||
}
|
||||
|
||||
// New new dapper collector
|
||||
func New(cfg *conf.Config) (*Collector, error) {
|
||||
daoImpl, err := dao.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bw := batchwrite.NewRawDataBatchWriter(
|
||||
daoImpl.WriteRawTrace,
|
||||
cfg.BatchWriter.RawBufSize,
|
||||
cfg.BatchWriter.RawChanSize,
|
||||
cfg.BatchWriter.RawWorkers,
|
||||
0,
|
||||
)
|
||||
|
||||
detectData := make(map[string]map[string]struct{})
|
||||
serviceNames, err := daoImpl.FetchServiceName(context.Background())
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
log.V(10).Info("fetch serviceNames get %d", len(serviceNames))
|
||||
for _, serviceName := range serviceNames {
|
||||
operationNames, err := daoImpl.FetchOperationName(context.Background(), serviceName)
|
||||
if err != nil {
|
||||
log.Error("fetch operationName for %s error: %s", serviceName, err)
|
||||
continue
|
||||
}
|
||||
log.V(10).Info("fetch operationName for %s get %d", serviceName, len(operationNames))
|
||||
detectData[serviceName] = make(map[string]struct{})
|
||||
for _, operationName := range operationNames {
|
||||
detectData[serviceName][operationName] = struct{}{}
|
||||
}
|
||||
}
|
||||
// TODO configable
|
||||
pw := pointwrite.New(daoImpl.BatchWriteSpanPoint, 5, 10*time.Second)
|
||||
|
||||
// register processer
|
||||
var process []Processer
|
||||
process = append(process, NewOperationNameProcess())
|
||||
// FIXME: breaker not work
|
||||
process = append(process, NewServiceBreakerProcess(4096))
|
||||
process = append(process, NewPeerServiceDetectProcesser(detectData))
|
||||
return &Collector{
|
||||
cfg: cfg,
|
||||
daoImpl: daoImpl,
|
||||
bw: bw,
|
||||
pw: pw,
|
||||
process: process,
|
||||
}, nil
|
||||
}
|
1
app/service/main/dapper/collector/collector_test.go
Normal file
1
app/service/main/dapper/collector/collector_test.go
Normal file
@ -0,0 +1 @@
|
||||
package collector
|
73
app/service/main/dapper/collector/detect.go
Normal file
73
app/service/main/dapper/collector/detect.go
Normal file
@ -0,0 +1,73 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"go-common/app/service/main/dapper/model"
|
||||
)
|
||||
|
||||
type peerServiceDetect struct {
|
||||
rmx sync.RWMutex
|
||||
pair map[string]string
|
||||
}
|
||||
|
||||
func (p *peerServiceDetect) detect(operationName string) (string, bool) {
|
||||
p.rmx.RLock()
|
||||
serviceName, ok := p.pair[operationName]
|
||||
p.rmx.RUnlock()
|
||||
return serviceName, ok
|
||||
}
|
||||
|
||||
func (p *peerServiceDetect) add(serviceName, operationName string) {
|
||||
if operationName == "" || serviceName == "" {
|
||||
// ignored empty
|
||||
return
|
||||
}
|
||||
p.rmx.RLock()
|
||||
val, ok := p.pair[operationName]
|
||||
p.rmx.RUnlock()
|
||||
if !ok || serviceName != val {
|
||||
p.rmx.Lock()
|
||||
p.pair[operationName] = serviceName
|
||||
p.rmx.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *peerServiceDetect) process(span *model.Span) {
|
||||
if span.IsServer() {
|
||||
p.add(span.ServiceName, span.OperationName)
|
||||
return
|
||||
}
|
||||
if span.GetTagString("peer.service") != "" {
|
||||
return
|
||||
}
|
||||
peerService, ok := p.detect(span.OperationName)
|
||||
if ok {
|
||||
span.SetTag("peer.service", peerService)
|
||||
span.SetTag("_auto.peer.service", true)
|
||||
return
|
||||
}
|
||||
if peerSign := span.StringTag("_peer.sign"); peerSign != "" {
|
||||
peerService, ok := p.detect(peerSign)
|
||||
if ok {
|
||||
span.SetTag("peer.service", peerService)
|
||||
span.SetTag("_auto.peer.service", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *peerServiceDetect) Process(span *model.Span) error {
|
||||
p.process(span)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPeerServiceDetectProcesser .
|
||||
func NewPeerServiceDetectProcesser(data map[string]map[string]struct{}) Processer {
|
||||
p := &peerServiceDetect{pair: make(map[string]string)}
|
||||
for serviceName, operationNames := range data {
|
||||
for operationName := range operationNames {
|
||||
p.add(serviceName, operationName)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
26
app/service/main/dapper/collector/detect_test.go
Normal file
26
app/service/main/dapper/collector/detect_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go-common/app/service/main/dapper/model"
|
||||
)
|
||||
|
||||
func TestPeerServiceDetect(t *testing.T) {
|
||||
detect := NewPeerServiceDetectProcesser(map[string]map[string]struct{}{
|
||||
"s_a": {
|
||||
"o_1": struct{}{},
|
||||
"o_11": struct{}{},
|
||||
},
|
||||
"s_b": {"o_2": struct{}{}},
|
||||
"s_c": {"o_21": struct{}{}},
|
||||
})
|
||||
sp1, _ := model.FromProtoSpan(&model.ProtoSpan{ServiceName: "xxx", OperationName: "o_1"}, false)
|
||||
detect.Process(sp1)
|
||||
if sp1.StringTag("peer.service") != "s_a" && sp1.BoolTag("_auto.peer.service") {
|
||||
t.Errorf("expect get s_a get %s", sp1.StringTag("peer.service"))
|
||||
}
|
||||
if err := detect.Process(&model.Span{ServiceName: "hh", OperationName: ""}); err != nil {
|
||||
t.Errorf("expect get noting")
|
||||
}
|
||||
}
|
46
app/service/main/dapper/collector/operation_name.go
Normal file
46
app/service/main/dapper/collector/operation_name.go
Normal file
@ -0,0 +1,46 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
import (
|
||||
"go-common/app/service/main/dapper/model"
|
||||
)
|
||||
|
||||
// OperationNameProcess fix operation name so sad!
|
||||
type OperationNameProcess struct{}
|
||||
|
||||
// Process implement operation name
|
||||
func (o *OperationNameProcess) Process(span *model.Span) error {
|
||||
switch {
|
||||
case !span.IsServer() && strings.HasPrefix(span.OperationName, "http://"):
|
||||
o.fixHTTP(span)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OperationNameProcess) fixHTTP(span *model.Span) {
|
||||
oldOperationName := span.OperationName
|
||||
method := "UNKONWN"
|
||||
if methodTag := span.GetTagString("http.method"); methodTag != "" {
|
||||
method = methodTag
|
||||
}
|
||||
operationName := "HTTP:" + method
|
||||
span.SetOperationName(operationName)
|
||||
|
||||
peerSign := oldOperationName
|
||||
if strings.HasPrefix(oldOperationName, "http://") {
|
||||
if reqURL, err := url.Parse(oldOperationName); err == nil {
|
||||
peerSign = reqURL.Path
|
||||
span.SetTag("http.url", oldOperationName)
|
||||
}
|
||||
}
|
||||
span.SetTag("_peer.sign", peerSign)
|
||||
}
|
||||
|
||||
// NewOperationNameProcess .
|
||||
func NewOperationNameProcess() Processer {
|
||||
return &OperationNameProcess{}
|
||||
}
|
27
app/service/main/dapper/collector/operation_name_test.go
Normal file
27
app/service/main/dapper/collector/operation_name_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go-common/app/service/main/dapper/model"
|
||||
protogen "go-common/library/net/trace/proto"
|
||||
)
|
||||
|
||||
func TestOperationNameProcess(t *testing.T) {
|
||||
p := NewOperationNameProcess()
|
||||
sp1, _ := model.FromProtoSpan(&model.ProtoSpan{
|
||||
ServiceName: "http",
|
||||
OperationName: "http://www.example.com/echo",
|
||||
Tags: []*protogen.Tag{&protogen.Tag{Key: "span.kind", Kind: protogen.Tag_STRING, Value: []byte("client")}},
|
||||
}, false)
|
||||
p.Process(sp1)
|
||||
if sp1.OperationName != "HTTP:UNKONWN" || sp1.ProtoSpan.OperationName != "HTTP:UNKONWN" {
|
||||
t.Errorf("expect operationName == , get %s %s", sp1.OperationName, sp1.ProtoSpan.OperationName)
|
||||
}
|
||||
if sp1.StringTag("http.url") != "http://www.example.com/echo" {
|
||||
t.Errorf("expect http.url be set")
|
||||
}
|
||||
if sp1.StringTag("_peer.sign") != "/echo" {
|
||||
t.Errorf("expect _peer.sign be set")
|
||||
}
|
||||
}
|
16
app/service/main/dapper/collector/process.go
Normal file
16
app/service/main/dapper/collector/process.go
Normal file
@ -0,0 +1,16 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"go-common/app/service/main/dapper/model"
|
||||
)
|
||||
|
||||
// Processer span processer
|
||||
type Processer interface {
|
||||
Process(span *model.Span) error
|
||||
}
|
||||
|
||||
// ProcessFunc implement Processer
|
||||
type ProcessFunc func(*model.Span) error
|
||||
|
||||
// Process implement Processer
|
||||
func (p ProcessFunc) Process(span *model.Span) error { return p(span) }
|
Reference in New Issue
Block a user