From 0e6f62b3e06994f3f8e3b10d1d03ff34b6aa9554 Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Thu, 16 May 2019 14:22:29 +0800 Subject: [PATCH 01/13] # Conflicts: # internal/client/driver/mysql/applier.go # internal/client/driver/mysql/extractor.go --- cmd/dtle/main.go | 24 +++++++ internal/client/driver/mysql/applier.go | 72 +++++++++++-------- .../driver/mysql/binlog/binlog_reader.go | 6 +- internal/client/driver/mysql/extractor.go | 59 ++++++++++++--- 4 files changed, 117 insertions(+), 44 deletions(-) diff --git a/cmd/dtle/main.go b/cmd/dtle/main.go index c10f0ba4f..34da56309 100644 --- a/cmd/dtle/main.go +++ b/cmd/dtle/main.go @@ -13,9 +13,13 @@ import ( "os" "github.com/mitchellh/cli" + "github.com/opentracing/opentracing-go" + "github.com/uber/jaeger-client-go/config" _ "net/http/pprof" + "time" + "github.com/actiontech/dtle/agent" "github.com/actiontech/dtle/cmd/dtle/command" ) @@ -28,6 +32,26 @@ var ( ) func main() { + cfg := config.Configuration{ + Sampler: &config.SamplerConfig{ + Type: "const", + Param: 1, + }, + ServiceName: "dtle_server", + Reporter: &config.ReporterConfig{ + LogSpans: true, + BufferFlushInterval: 1 * time.Second, + LocalAgentHostPort: "10.186.63.111:5775", // 数据上报地址 + }, + } + + tracer, closer, err := cfg.NewTracer() + if err != nil { + log.Panic(err) + } + opentracing.SetGlobalTracer(tracer) + defer closer.Close() + os.Exit(realMain()) } diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index cdfb2f050..e83099710 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -12,6 +12,9 @@ import ( "fmt" "github.com/actiontech/dtle/internal/g" + "github.com/not.go" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" //"math" "bytes" @@ -241,10 +244,6 @@ type Applier struct { nDumpEntry int64 stubFullApplyDelay bool - - // TODO we might need to save these from DumpEntry for reconnecting. - //SystemVariablesStatement string - //SqlMode string } func NewApplier(subject, tp string, cfg *config.MySQLDriverConfig, logger *log.Logger) (*Applier, error) { @@ -496,23 +495,6 @@ func (a *Applier) initNatSubClient() (err error) { return nil } -func DecodeDumpEntry(data []byte) (entry *DumpEntry, err error) { - msg, err := snappy.Decode(nil, data) - if err != nil { - return nil, err - } - - entry = &DumpEntry{} - n, err := entry.Unmarshal(msg) - if err != nil { - return nil, err - } - if n != uint64(len(msg)) { - return nil, fmt.Errorf("DumpEntry.Unmarshal: not all consumed. data: %v, consumed: %v", - len(msg), n) - } - return entry, nil -} // Decode func Decode(data []byte, vPtr interface{}) (err error) { msg, err := snappy.Decode(nil, data) @@ -821,13 +803,23 @@ func (a *Applier) initiateStreaming() error { return err }*/ - _, err = a.natsConn.Subscribe(fmt.Sprintf("%s_full_complete", a.subject), func(m *gonats.Msg) { - dumpData := &dumpStatResult{} - if err := Decode(m.Data, dumpData); err != nil { - a.onError(TaskStateDead, err) - } - a.currentCoordinates.RetrievedGtidSet = dumpData.Gtid - a.mysqlContext.Stage = models.StageSlaveWaitingForWorkersToProcessQueue + _, err = a.natsConn.Subscribe(fmt.Sprintf("%s_full_complete", a.subject), func(m *gonats.Msg) { + dumpData := &dumpStatResult{} + t := not.NewTraceMsg(m) + // Extract the span context from the request message. + sc, err := tracer.Extract(opentracing.Binary, t) + if err != nil { + a.logger.Debugf("applier:get data") + } + // Setup a span referring to the span context of the incoming NATS message. + replySpan := tracer.StartSpan("Service Responder", ext.SpanKindRPCServer, ext.RPCServerOption(sc)) + ext.MessageBusDestination.Set(replySpan, m.Subject) + defer replySpan.Finish() + if err := Decode(t.Bytes(), dumpData); err != nil { + a.onError(TaskStateDead, err) + } + a.currentCoordinates.RetrievedGtidSet = dumpData.Gtid + a.mysqlContext.Stage = models.StageSlaveWaitingForWorkersToProcessQueue for atomic.LoadInt64(&a.nDumpEntry) != 0 { a.logger.Debugf("mysql.applier. nDumpEntry is not zero, waiting. %v", a.nDumpEntry) @@ -851,7 +843,17 @@ func (a *Applier) initiateStreaming() error { if a.mysqlContext.ApproveHeterogeneous { _, err := a.natsConn.Subscribe(fmt.Sprintf("%s_incr_hete", a.subject), func(m *gonats.Msg) { var binlogEntries binlog.BinlogEntries - if err := Decode(m.Data, &binlogEntries); err != nil { + t := not.NewTraceMsg(m) + // Extract the span context from the request message. + spanContext, err := tracer.Extract(opentracing.Binary, t) + if err != nil { + a.logger.Debugf("applier:get data") + } + // Setup a span referring to the span context of the incoming NATS message. + replySpan := tracer.StartSpan("nast Subscribe ", ext.SpanKindRPCServer, ext.RPCServerOption(spanContext)) + ext.MessageBusDestination.Set(replySpan, m.Subject) + defer replySpan.Finish() + if err := Decode(t.Bytes(), &binlogEntries); err != nil { a.onError(TaskStateDead, err) } @@ -895,7 +897,17 @@ func (a *Applier) initiateStreaming() error { } else { _, err := a.natsConn.Subscribe(fmt.Sprintf("%s_incr", a.subject), func(m *gonats.Msg) { var binlogTx []*binlog.BinlogTx - if err := Decode(m.Data, &binlogTx); err != nil { + t := not.NewTraceMsg(m) + // Extract the span context from the request message. + sc, err := tracer.Extract(opentracing.Binary, t) + if err != nil { + a.logger.Debugf("applier:get data") + } + // Setup a span referring to the span context of the incoming NATS message. + replySpan := tracer.StartSpan(a.subject, ext.SpanKindRPCServer, ext.RPCServerOption(sc)) + ext.MessageBusDestination.Set(replySpan, m.Subject) + defer replySpan.Finish() + if err := Decode(t.Bytes(), &binlogTx); err != nil { a.onError(TaskStateDead, err) } for _, tx := range binlogTx { diff --git a/internal/client/driver/mysql/binlog/binlog_reader.go b/internal/client/driver/mysql/binlog/binlog_reader.go index 055f0bab9..d9a353f94 100644 --- a/internal/client/driver/mysql/binlog/binlog_reader.go +++ b/internal/client/driver/mysql/binlog/binlog_reader.go @@ -12,7 +12,6 @@ import ( "time" "github.com/actiontech/dtle/internal/g" - //"encoding/hex" "fmt" "regexp" @@ -24,10 +23,10 @@ import ( "github.com/issuj/gofaster/base64" "github.com/pingcap/parser" - ast "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/ast" _ "github.com/pingcap/tidb/types/parser_driver" - uuid "github.com/satori/go.uuid" + "github.com/satori/go.uuid" gomysql "github.com/siddontang/go-mysql/mysql" "github.com/siddontang/go-mysql/replication" "golang.org/x/net/context" @@ -705,7 +704,6 @@ func (b *BinlogReader) DataStreamEvents(entriesChannel chan<- *BinlogEntry) erro if b.shutdown { break } - ev, err := b.binlogStreamer.GetEvent(context.Background()) if err != nil { b.logger.Errorf("mysql.reader error GetEvent. err: %v", err) diff --git a/internal/client/driver/mysql/extractor.go b/internal/client/driver/mysql/extractor.go index ebc88f8f2..6d2fd4391 100644 --- a/internal/client/driver/mysql/extractor.go +++ b/internal/client/driver/mysql/extractor.go @@ -13,6 +13,8 @@ import ( "github.com/actiontech/dtle/internal/g" "github.com/pkg/errors" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" //"math" "bytes" @@ -26,6 +28,7 @@ import ( "github.com/golang/snappy" gonats "github.com/nats-io/go-nats" + "github.com/not.go" gomysql "github.com/siddontang/go-mysql/mysql" "os" @@ -34,6 +37,8 @@ import ( "net" + "context" + "github.com/actiontech/dtle/internal/client/driver/mysql/base" "github.com/actiontech/dtle/internal/client/driver/mysql/binlog" "github.com/actiontech/dtle/internal/client/driver/mysql/sql" @@ -225,6 +230,10 @@ func (e *Extractor) Run() { } if fullCopy { + var ctx context.Context + span := opentracing.GlobalTracer().StartSpan("span_full_complete") + span.Finish() + ctx = opentracing.ContextWithSpan(ctx, span) e.mysqlContext.MarkRowCopyStartTime() if err := e.mysqlDump(); err != nil { e.onError(TaskStateDead, err) @@ -234,7 +243,7 @@ func (e *Extractor) Run() { if err != nil { e.onError(TaskStateDead, err) } - if err := e.publish(fmt.Sprintf("%s_full_complete", e.subject), "", dumpMsg); err != nil { + if err := e.publish(ctx, fmt.Sprintf("%s_full_complete", e.subject), "", dumpMsg); err != nil { e.onError(TaskStateDead, err) } } else { @@ -794,6 +803,11 @@ func Encode(v interface{}) ([]byte, error) { // StreamEvents will begin streaming events. It will be blocking, so should be // executed by a goroutine func (e *Extractor) StreamEvents() error { + var ctx context.Context + //tracer := opentracing.GlobalTracer() + span := opentracing.GlobalTracer().StartSpan("span_incr_hete") + defer span.Finish() + ctx = opentracing.ContextWithSpan(ctx, span) if e.mysqlContext.ApproveHeterogeneous { go func() { defer e.logger.Debugf("extractor. StreamEvents goroutine exited") @@ -812,7 +826,7 @@ func (e *Extractor) StreamEvents() error { return err } e.logger.Debugf("mysql.extractor: sending gno: %v, n: %v", gno, len(entries.Entries)) - if err = e.publish(fmt.Sprintf("%s_incr_hete", e.subject), "", txMsg); err != nil { + if err = e.publish(ctx, fmt.Sprintf("%s_incr_hete", e.subject), "", txMsg); err != nil { return err } e.logger.Debugf("mysql.extractor: send acked gno: %v, n: %v", gno, len(entries.Entries)) @@ -978,7 +992,7 @@ func (e *Extractor) StreamEvents() error { if len(txMsg) > e.maxPayload { e.onError(TaskStateDead, gonats.ErrMaxPayload) } - if err = e.publish(subject, fmt.Sprintf("%s:1-%d", binlogTx.SID, binlogTx.GNO), txMsg); err != nil { + if err = e.publish(ctx, subject, fmt.Sprintf("%s:1-%d", binlogTx.SID, binlogTx.GNO), txMsg); err != nil { e.onError(TaskStateDead, err) break L } @@ -999,7 +1013,7 @@ func (e *Extractor) StreamEvents() error { if len(txMsg) > e.maxPayload { e.onError(TaskStateDead, gonats.ErrMaxPayload) } - if err = e.publish(subject, + if err = e.publish(ctx, subject, fmt.Sprintf("%s:1-%d", txArray[len(txArray)-1].SID, txArray[len(txArray)-1].GNO), @@ -1033,10 +1047,32 @@ func (e *Extractor) StreamEvents() error { // retryOperation attempts up to `count` attempts at running given function, // exiting as soon as it returns with non-error. -func (e *Extractor) publish(subject, gtid string, txMsg []byte) (err error) { +func (e *Extractor) publish(ctx context.Context, subject, gtid string, txMsg []byte) (err error) { + tracer := opentracing.GlobalTracer() + var t not.TraceMsg + var spanctx opentracing.SpanContext + if ctx != nil { + spanctx = opentracing.SpanFromContext(ctx).Context() + } else { + parent := tracer.StartSpan("no parent ", ext.SpanKindProducer) + defer parent.Finish() + spanctx = parent.Context() + } + + span := tracer.StartSpan("Nast Publish Message", ext.SpanKindProducer, opentracing.ChildOf(spanctx)) + + ext.MessageBusDestination.Set(span, subject) + + // Inject span context into our traceMsg. + if err := tracer.Inject(span.Context(), opentracing.Binary, &t); err != nil { + e.logger.Debugf("mysql.extractor: start tracer fail, got %v", err) + } + // Add the payload. + t.Write(txMsg) + defer span.Finish() for { e.logger.Debugf("mysql.extractor: publish. gtid: %v, msg_len: %v", gtid, len(txMsg)) - _, err = e.natsConn.Request(subject, txMsg, DefaultConnectWait) + _, err = e.natsConn.Request(subject, t.Bytes(), DefaultConnectWait) if err == nil { if gtid != "" { e.mysqlContext.Gtid = gtid @@ -1390,13 +1426,16 @@ func (e *Extractor) mysqlDump() error { return nil } func (e *Extractor) encodeDumpEntry(entry *DumpEntry) error { - bs, err := entry.Marshal(nil) + var ctx context.Context + //tracer := opentracing.GlobalTracer() + span := opentracing.GlobalTracer().StartSpan("span_full") + span.Finish() + ctx = opentracing.ContextWithSpan(ctx, span) + txMsg, err := Encode(entry) if err != nil { return err } - txMsg := snappy.Encode(nil, bs) - - if err := e.publish(fmt.Sprintf("%s_full", e.subject), "", txMsg); err != nil { + if err := e.publish(ctx, fmt.Sprintf("%s_full", e.subject), "", txMsg); err != nil { return err } e.mysqlContext.Stage = models.StageSendingData From be1e123c561735351469095c6f9ca609c58efe06 Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Wed, 31 Jul 2019 21:33:41 +0800 Subject: [PATCH 02/13] # Conflicts: # internal/client/driver/mysql/applier.go # internal/client/driver/mysql/extractor.go --- internal/client/driver/mysql/applier.go | 49 ++++++++++++++--------- internal/client/driver/mysql/extractor.go | 22 +++++----- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index e83099710..6e9b84f90 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -12,7 +12,6 @@ import ( "fmt" "github.com/actiontech/dtle/internal/g" - "github.com/not.go" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" @@ -771,10 +770,20 @@ OUTER: func (a *Applier) initiateStreaming() error { a.mysqlContext.MarkRowCopyStartTime() a.logger.Debugf("mysql.applier: nats subscribe") + tracer := opentracing.GlobalTracer() _, err := a.natsConn.Subscribe(fmt.Sprintf("%s_full", a.subject), func(m *gonats.Msg) { a.logger.Debugf("mysql.applier: full. recv a msg. copyRowsQueue: %v", len(a.copyRowsQueue)) - - dumpData, err := DecodeDumpEntry(m.Data) + t := not.NewTraceMsg(m) + // Extract the span context from the request message. + sc, err := tracer.Extract(opentracing.Binary, t) + if err != nil { + a.logger.Debugf("applier:get data") + } + // Setup a span referring to the span context of the incoming NATS message. + replySpan := tracer.StartSpan("Service Responder", ext.SpanKindRPCServer, ext.RPCServerOption(sc)) + ext.MessageBusDestination.Set(replySpan, m.Subject) + defer replySpan.Finish() + dumpData, err := DecodeDumpEntry(t.Bytes()) if err != nil { a.onError(TaskStateDead, err) // TODO return? @@ -803,23 +812,23 @@ func (a *Applier) initiateStreaming() error { return err }*/ - _, err = a.natsConn.Subscribe(fmt.Sprintf("%s_full_complete", a.subject), func(m *gonats.Msg) { - dumpData := &dumpStatResult{} - t := not.NewTraceMsg(m) - // Extract the span context from the request message. - sc, err := tracer.Extract(opentracing.Binary, t) - if err != nil { - a.logger.Debugf("applier:get data") - } - // Setup a span referring to the span context of the incoming NATS message. - replySpan := tracer.StartSpan("Service Responder", ext.SpanKindRPCServer, ext.RPCServerOption(sc)) - ext.MessageBusDestination.Set(replySpan, m.Subject) - defer replySpan.Finish() - if err := Decode(t.Bytes(), dumpData); err != nil { - a.onError(TaskStateDead, err) - } - a.currentCoordinates.RetrievedGtidSet = dumpData.Gtid - a.mysqlContext.Stage = models.StageSlaveWaitingForWorkersToProcessQueue + _, err = a.natsConn.Subscribe(fmt.Sprintf("%s_full_complete", a.subject), func(m *gonats.Msg) { + dumpData := &dumpStatResult{} + t := not.NewTraceMsg(m) + // Extract the span context from the request message. + sc, err := tracer.Extract(opentracing.Binary, t) + if err != nil { + a.logger.Debugf("applier:get data") + } + // Setup a span referring to the span context of the incoming NATS message. + replySpan := tracer.StartSpan("Service Responder", ext.SpanKindRPCServer, ext.RPCServerOption(sc)) + ext.MessageBusDestination.Set(replySpan, m.Subject) + defer replySpan.Finish() + if err := Decode(t.Bytes(), dumpData); err != nil { + a.onError(TaskStateDead, err) + } + a.currentCoordinates.RetrievedGtidSet = dumpData.Gtid + a.mysqlContext.Stage = models.StageSlaveWaitingForWorkersToProcessQueue for atomic.LoadInt64(&a.nDumpEntry) != 0 { a.logger.Debugf("mysql.applier. nDumpEntry is not zero, waiting. %v", a.nDumpEntry) diff --git a/internal/client/driver/mysql/extractor.go b/internal/client/driver/mysql/extractor.go index 6d2fd4391..4ab48b3d0 100644 --- a/internal/client/driver/mysql/extractor.go +++ b/internal/client/driver/mysql/extractor.go @@ -12,9 +12,9 @@ import ( "fmt" "github.com/actiontech/dtle/internal/g" - "github.com/pkg/errors" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" + "github.com/pkg/errors" //"math" "bytes" @@ -28,7 +28,6 @@ import ( "github.com/golang/snappy" gonats "github.com/nats-io/go-nats" - "github.com/not.go" gomysql "github.com/siddontang/go-mysql/mysql" "os" @@ -63,15 +62,15 @@ const ( // Extractor is the main schema extract flow manager. type Extractor struct { - logger *log.Entry - subject string - tp string - maxPayload int - mysqlContext *config.MySQLDriverConfig + logger *log.Entry + subject string + tp string + maxPayload int + mysqlContext *config.MySQLDriverConfig mysqlVersionDigit int - db *gosql.DB - singletonDB *gosql.DB - dumpers []*dumper + db *gosql.DB + singletonDB *gosql.DB + dumpers []*dumper // db.tb exists when creating the job, for full-copy. // vs e.mysqlContext.ReplicateDoDb: all user assigned db.tb replicateDoDb []*config.DataSource @@ -1431,10 +1430,11 @@ func (e *Extractor) encodeDumpEntry(entry *DumpEntry) error { span := opentracing.GlobalTracer().StartSpan("span_full") span.Finish() ctx = opentracing.ContextWithSpan(ctx, span) - txMsg, err := Encode(entry) + bs, err := entry.Marshal(nil) if err != nil { return err } + txMsg := snappy.Encode(nil, bs) if err := e.publish(ctx, fmt.Sprintf("%s_full", e.subject), "", txMsg); err != nil { return err } From bf82e2e964e3594f03492f239989fb45de6f2426 Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Fri, 24 May 2019 16:54:42 +0800 Subject: [PATCH 03/13] add tracing --- internal/client/driver/mysql/extractor.go | 11 +++++------ .../siddontang/go-mysql/replication/binlogstreamer.go | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/client/driver/mysql/extractor.go b/internal/client/driver/mysql/extractor.go index 4ab48b3d0..715631a8b 100644 --- a/internal/client/driver/mysql/extractor.go +++ b/internal/client/driver/mysql/extractor.go @@ -231,7 +231,7 @@ func (e *Extractor) Run() { if fullCopy { var ctx context.Context span := opentracing.GlobalTracer().StartSpan("span_full_complete") - span.Finish() + defer span.Finish() ctx = opentracing.ContextWithSpan(ctx, span) e.mysqlContext.MarkRowCopyStartTime() if err := e.mysqlDump(); err != nil { @@ -805,12 +805,11 @@ func (e *Extractor) StreamEvents() error { var ctx context.Context //tracer := opentracing.GlobalTracer() span := opentracing.GlobalTracer().StartSpan("span_incr_hete") - defer span.Finish() ctx = opentracing.ContextWithSpan(ctx, span) + span.Finish() if e.mysqlContext.ApproveHeterogeneous { go func() { defer e.logger.Debugf("extractor. StreamEvents goroutine exited") - entries := binlog.BinlogEntries{} entriesSize := 0 @@ -871,7 +870,7 @@ func (e *Extractor) StreamEvents() error { e.logger.Debugf("mysql.extractor: err is : %v", err != nil) if entriesSize >= e.mysqlContext.GroupMaxSize || int64(len(entries.Entries)) == e.mysqlContext.ReplChanBufferSize { - e.logger.Debugf("extractor. incr. send by GroupLimit. entriesSize: %v", entriesSize) + e.logger.Debugf("extractor. incr. send by GroupLimit. entriesSize: %v , groupMaxSize: %v,Entries.len: %v", entriesSize, e.mysqlContext.GroupMaxSize, len(entries.Entries)) err = sendEntries() if !timer.Stop() { <-timer.C @@ -881,7 +880,7 @@ func (e *Extractor) StreamEvents() error { case <-timer.C: nEntries := len(entries.Entries) if nEntries > 0 { - e.logger.Debugf("extractor. incr. send by timeout. entriesSize: %v", entriesSize) + e.logger.Debugf("extractor. incr. send by timeout. entriesSize: %v,timeout time: %v", entriesSize, e.mysqlContext.GroupTimeout) err = sendEntries() } timer.Reset(groupTimeoutDuration) @@ -1428,7 +1427,7 @@ func (e *Extractor) encodeDumpEntry(entry *DumpEntry) error { var ctx context.Context //tracer := opentracing.GlobalTracer() span := opentracing.GlobalTracer().StartSpan("span_full") - span.Finish() + defer span.Finish() ctx = opentracing.ContextWithSpan(ctx, span) bs, err := entry.Marshal(nil) if err != nil { diff --git a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go index 2dcd0c161..8a24dcaa3 100644 --- a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go +++ b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go @@ -36,7 +36,7 @@ func (s *BinlogStreamer) GetEvent(ctx context.Context) (*BinlogEvent, error) { } } -// DumpEvents dumps all left events +// DumpEvents dumps all left eventsmax_payload func (s *BinlogStreamer) DumpEvents() []*BinlogEvent { count := len(s.ch) events := make([]*BinlogEvent, 0, count) From 90c5b20509361a7784ff2f8720a05540a63353c3 Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Tue, 28 May 2019 14:58:26 +0800 Subject: [PATCH 04/13] # Conflicts: # internal/client/driver/mysql/extractor.go --- internal/client/driver/mysql/applier.go | 24 +++++++++++++++---- .../driver/mysql/binlog/binlog_entry.go | 7 +++--- .../driver/mysql/binlog/binlog_reader.go | 20 ++++++++++++++++ internal/client/driver/mysql/extractor.go | 13 ++++++---- .../go-mysql/replication/binlogsyncer.go | 16 +++++++++---- .../siddontang/go-mysql/replication/event.go | 6 +++-- .../siddontang/go-mysql/replication/parser.go | 2 +- 7 files changed, 69 insertions(+), 19 deletions(-) diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index 6e9b84f90..d61229847 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -289,7 +289,7 @@ func (a *Applier) MtsWorker(workerIndex int) { case tx := <-a.applyBinlogMtsTxQueue: a.logger.Debugf("mysql.applier: a binlogEntry MTS dequeue, worker: %v. GNO: %v", workerIndex, tx.Coordinates.GNO) - if err := a.ApplyBinlogEvent(workerIndex, tx); err != nil { + if err := a.ApplyBinlogEvent(nil, workerIndex, tx); err != nil { a.onError(TaskStateDead, err) // TODO coordinate with other goroutine keepLoop = false } else { @@ -571,13 +571,16 @@ func (a *Applier) heterogeneousReplay() { var err error stopSomeLoop := false prevDDL := false + var ctx context.Context for !stopSomeLoop { select { case binlogEntry := <-a.applyDataEntryQueue: if nil == binlogEntry { continue } - + spanContext := binlogEntry.SpanContext + span := opentracing.GlobalTracer().StartSpan("desc dtle get binlogEntry", opentracing.ChildOf(spanContext)) + ctx = opentracing.ContextWithSpan(ctx, span) a.logger.Debugf("mysql.applier: a binlogEntry. remaining: %v. gno: %v, lc: %v, seq: %v", len(a.applyDataEntryQueue), binlogEntry.Coordinates.GNO, binlogEntry.Coordinates.LastCommitted, binlogEntry.Coordinates.SeqenceNumber) @@ -645,7 +648,7 @@ func (a *Applier) heterogeneousReplay() { a.onError(TaskStateDead, err) return } - if err := a.ApplyBinlogEvent(0, binlogEntry); err != nil { + if err := a.ApplyBinlogEvent(ctx, 0, binlogEntry); err != nil { a.onError(TaskStateDead, err) return } @@ -705,8 +708,10 @@ func (a *Applier) heterogeneousReplay() { a.onError(TaskStateDead, err) return } + binlogEntry.SpanContext = span.Context() a.applyBinlogMtsTxQueue <- binlogEntry } + span.Finish() if !a.shutdown { // TODO what is this used for? a.mysqlContext.Gtid = fmt.Sprintf("%s:1-%d", txSid, binlogEntry.Coordinates.GNO) @@ -878,6 +883,7 @@ func (a *Applier) initiateStreaming() error { } else { a.logger.Debugf("applier. incr. applyDataEntryQueue enqueue") for _, binlogEntry := range binlogEntries.Entries { + binlogEntry.SpanContext = replySpan.Context() a.applyDataEntryQueue <- binlogEntry a.currentCoordinates.RetrievedGtidSet = binlogEntry.Coordinates.GetGtidForThisTx() atomic.AddInt64(&a.mysqlContext.DeltaEstimate, 1) @@ -1258,12 +1264,20 @@ func (a *Applier) buildDMLEventQuery(dmlEvent binlog.DataEvent, workerIdx int) ( } // ApplyEventQueries applies multiple DML queries onto the dest table -func (a *Applier) ApplyBinlogEvent(workerIdx int, binlogEntry *binlog.BinlogEntry) error { +func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEntry *binlog.BinlogEntry) error { dbApplier := a.dbs[workerIdx] var totalDelta int64 var err error - + if ctx != nil { + spanContext := opentracing.SpanFromContext(ctx).Context() + span := opentracing.GlobalTracer().StartSpan("single sql replace into desc ", opentracing.ChildOf(spanContext)) + defer span.Finish() + } else { + spanContext := binlogEntry.SpanContext + span := opentracing.GlobalTracer().StartSpan("mts sql replace into desc ", opentracing.ChildOf(spanContext)) + defer span.Finish() + } txSid := binlogEntry.Coordinates.GetSid() dbApplier.DbMutex.Lock() diff --git a/internal/client/driver/mysql/binlog/binlog_entry.go b/internal/client/driver/mysql/binlog/binlog_entry.go index c647ac137..8cb4f2186 100644 --- a/internal/client/driver/mysql/binlog/binlog_entry.go +++ b/internal/client/driver/mysql/binlog/binlog_entry.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/actiontech/dtle/internal/client/driver/mysql/base" + opentracing "github.com/opentracing/opentracing-go" ) type BinlogEntries struct { @@ -20,9 +21,9 @@ type BinlogEntries struct { type BinlogEntry struct { hasBeginQuery bool Coordinates base.BinlogCoordinateTx - - Events []DataEvent - OriginalSize int // size of binlog entry + SpanContext opentracing.SpanContext + Events []DataEvent + OriginalSize int // size of binlog entry } // NewBinlogEntry creates an empty, ready to go BinlogEntry object diff --git a/internal/client/driver/mysql/binlog/binlog_reader.go b/internal/client/driver/mysql/binlog/binlog_reader.go index d9a353f94..fbddcd7e9 100644 --- a/internal/client/driver/mysql/binlog/binlog_reader.go +++ b/internal/client/driver/mysql/binlog/binlog_reader.go @@ -38,9 +38,12 @@ import ( "github.com/actiontech/dtle/internal/config" "github.com/actiontech/dtle/internal/config/mysql" + "time" + log "github.com/actiontech/dtle/internal/logger" "github.com/actiontech/dtle/internal/models" "github.com/actiontech/dtle/utils" + "github.com/opentracing/opentracing-go" ) // BinlogReader is a general interface whose implementations can choose their methods of reading @@ -308,6 +311,11 @@ type parseDDLResult struct { // StreamEvents func (b *BinlogReader) handleEvent(ev *replication.BinlogEvent, entriesChannel chan<- *BinlogEntry) error { + spanContext := ev.SpanContest + trace := opentracing.GlobalTracer() + span := trace.StartSpan("inc tx to sql", opentracing.ChildOf(spanContext)) + span.SetTag("time", time.Now().Unix()) + defer span.Finish() if b.currentCoordinates.SmallerThanOrEquals(&b.LastAppliedRowsEventHint) { b.logger.Debugf("mysql.reader: Skipping handled query at %+v", b.currentCoordinates) return nil @@ -377,6 +385,7 @@ func (b *BinlogReader) handleEvent(ev *replication.BinlogEvent, entriesChannel c NotDML, ) b.currentBinlogEntry.Events = append(b.currentBinlogEntry.Events, event) + b.currentBinlogEntry.SpanContext = span.Context() entriesChannel <- b.currentBinlogEntry b.LastAppliedRowsEventHint = b.currentCoordinates return nil @@ -519,11 +528,13 @@ func (b *BinlogReader) handleEvent(ev *replication.BinlogEvent, entriesChannel c b.currentBinlogEntry.Events = append(b.currentBinlogEntry.Events, event) } } + b.currentBinlogEntry.SpanContext = span.Context() entriesChannel <- b.currentBinlogEntry b.LastAppliedRowsEventHint = b.currentCoordinates } } case replication.XID_EVENT: + b.currentBinlogEntry.SpanContext = span.Context() entriesChannel <- b.currentBinlogEntry b.LastAppliedRowsEventHint = b.currentCoordinates default: @@ -704,11 +715,19 @@ func (b *BinlogReader) DataStreamEvents(entriesChannel chan<- *BinlogEntry) erro if b.shutdown { break } + + trace := opentracing.GlobalTracer() + ev, err := b.binlogStreamer.GetEvent(context.Background()) if err != nil { b.logger.Errorf("mysql.reader error GetEvent. err: %v", err) return err } + spanContext := ev.SpanContest + span := trace.StartSpan("get event from mysql-go ", opentracing.ChildOf(spanContext)) + span.SetTag("time", time.Now().Unix()) + ev.SpanContest = span.Context() + if ev.Header.EventType == replication.HEARTBEAT_EVENT { continue } @@ -737,6 +756,7 @@ func (b *BinlogReader) DataStreamEvents(entriesChannel chan<- *BinlogEntry) erro return err } } + span.Finish() } return nil diff --git a/internal/client/driver/mysql/extractor.go b/internal/client/driver/mysql/extractor.go index 715631a8b..2c341a8fd 100644 --- a/internal/client/driver/mysql/extractor.go +++ b/internal/client/driver/mysql/extractor.go @@ -28,6 +28,7 @@ import ( "github.com/golang/snappy" gonats "github.com/nats-io/go-nats" + "github.com/not.go" gomysql "github.com/siddontang/go-mysql/mysql" "os" @@ -804,15 +805,12 @@ func Encode(v interface{}) ([]byte, error) { func (e *Extractor) StreamEvents() error { var ctx context.Context //tracer := opentracing.GlobalTracer() - span := opentracing.GlobalTracer().StartSpan("span_incr_hete") - ctx = opentracing.ContextWithSpan(ctx, span) - span.Finish() + if e.mysqlContext.ApproveHeterogeneous { go func() { defer e.logger.Debugf("extractor. StreamEvents goroutine exited") entries := binlog.BinlogEntries{} entriesSize := 0 - sendEntries := func() error { var gno int64 = 0 if len(entries.Entries) > 0 { @@ -847,6 +845,12 @@ func (e *Extractor) StreamEvents() error { var addrs []net.Addr select { case binlogEntry := <-e.dataChannel: + spanContext := binlogEntry.SpanContext + span := opentracing.GlobalTracer().StartSpan("span_incr_hete", opentracing.ChildOf(spanContext)) + span.SetTag("time", time.Now().Unix()) + ctx = opentracing.ContextWithSpan(ctx, span) + //span.SetTag("timetag", time.Now().Unix()) + binlogEntry.SpanContext = nil entries.Entries = append(entries.Entries, binlogEntry) entriesSize += binlogEntry.OriginalSize if int64(len(entries.Entries)) <= 1 { @@ -877,6 +881,7 @@ func (e *Extractor) StreamEvents() error { } timer.Reset(groupTimeoutDuration) } + span.Finish() case <-timer.C: nEntries := len(entries.Entries) if nEntries > 0 { diff --git a/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go b/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go index 371212f4b..2d831ec79 100644 --- a/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go +++ b/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go @@ -11,6 +11,7 @@ import ( "time" "github.com/juju/errors" + "github.com/opentracing/opentracing-go" "github.com/satori/go.uuid" "github.com/siddontang/go-log/log" "github.com/siddontang/go-mysql/client" @@ -618,7 +619,10 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) { }() for { + span := opentracing.StartSpan(" get inc tx from ReadPacket()") + span.SetTag("begin to get data time ", time.Now().Unix()) data, err := b.c.ReadPacket() + span.SetTag("after get data time ", time.Now().Unix()) if err != nil { log.Error(err) @@ -655,7 +659,6 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) { // we connect the server and begin to re-sync again. continue } - //set read timeout if b.cfg.ReadTimeout > 0 { b.c.SetReadDeadline(time.Now().Add(b.cfg.ReadTimeout)) @@ -666,7 +669,7 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) { switch data[0] { case OK_HEADER: - if err = b.parseEvent(s, data); err != nil { + if err = b.parseEvent(span.Context(), s, data); err != nil { s.closeWithError(err) return } @@ -685,13 +688,16 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) { log.Errorf("invalid stream header %c", data[0]) continue } + span.Finish() } } -func (b *BinlogSyncer) parseEvent(s *BinlogStreamer, data []byte) error { +func (b *BinlogSyncer) parseEvent(spanContext opentracing.SpanContext, s *BinlogStreamer, data []byte) error { //skip OK byte, 0x00 data = data[1:] - + span := opentracing.GlobalTracer().StartSpan(" inc tx to BinlogEvent", opentracing.ChildOf(spanContext)) + span.SetTag("time", time.Now().Unix()) + defer span.Finish() needACK := false if b.cfg.SemiSyncEnabled && (data[0] == SemiSyncIndicator) { needACK = (data[1] == 0x01) @@ -700,6 +706,8 @@ func (b *BinlogSyncer) parseEvent(s *BinlogStreamer, data []byte) error { } e, err := b.parser.Parse(data) + e.SpanContest = span.Context() + span.SetTag("tx timestap", e.Header.Timestamp) if err != nil { return errors.Trace(err) } diff --git a/vendor/github.com/siddontang/go-mysql/replication/event.go b/vendor/github.com/siddontang/go-mysql/replication/event.go index 737b431a0..0c102abf3 100644 --- a/vendor/github.com/siddontang/go-mysql/replication/event.go +++ b/vendor/github.com/siddontang/go-mysql/replication/event.go @@ -10,6 +10,7 @@ import ( "unicode" "github.com/juju/errors" + "github.com/opentracing/opentracing-go" "github.com/satori/go.uuid" . "github.com/siddontang/go-mysql/mysql" ) @@ -26,8 +27,9 @@ type BinlogEvent struct { // raw binlog data which contains all data, including binlog header and event body, and including crc32 checksum if exists RawData []byte - Header *EventHeader - Event Event + Header *EventHeader + Event Event + SpanContest opentracing.SpanContext } func (e *BinlogEvent) Dump(w io.Writer) { diff --git a/vendor/github.com/siddontang/go-mysql/replication/parser.go b/vendor/github.com/siddontang/go-mysql/replication/parser.go index 221122999..7698a35b9 100644 --- a/vendor/github.com/siddontang/go-mysql/replication/parser.go +++ b/vendor/github.com/siddontang/go-mysql/replication/parser.go @@ -317,7 +317,7 @@ func (p *BinlogParser) Parse(data []byte) (*BinlogEvent, error) { return nil, err } - return &BinlogEvent{RawData: rawData, Header: h, Event: e}, nil + return &BinlogEvent{RawData: rawData, Header: h, Event: e, SpanContest: nil}, nil } func (p *BinlogParser) verifyCrc32Checksum(rawData []byte) error { From ae5cda060a0c8266f02b88b632c8c1f2e0d17090 Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Wed, 31 Jul 2019 21:48:00 +0800 Subject: [PATCH 05/13] # Conflicts: # internal/client/driver/mysql/extractor.go --- internal/client/driver/mysql/extractor.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/client/driver/mysql/extractor.go b/internal/client/driver/mysql/extractor.go index 2c341a8fd..139450aa9 100644 --- a/internal/client/driver/mysql/extractor.go +++ b/internal/client/driver/mysql/extractor.go @@ -28,7 +28,6 @@ import ( "github.com/golang/snappy" gonats "github.com/nats-io/go-nats" - "github.com/not.go" gomysql "github.com/siddontang/go-mysql/mysql" "os" From b8666b7d942c45b01666c8834c75949cc41c472f Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Tue, 4 Jun 2019 11:00:32 +0800 Subject: [PATCH 06/13] add go-mysql send tracing --- .../siddontang/go-mysql/replication/binlogstreamer.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go index 8a24dcaa3..25c70d586 100644 --- a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go +++ b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go @@ -3,7 +3,10 @@ package replication import ( "context" + "time" + "github.com/juju/errors" + "github.com/opentracing/opentracing-go" "github.com/siddontang/go-log/log" ) @@ -28,6 +31,10 @@ func (s *BinlogStreamer) GetEvent(ctx context.Context) (*BinlogEvent, error) { select { case c := <-s.ch: + span := opentracing.StartSpan("send event from go mysql", opentracing.ChildOf(c.SpanContest)) + span.SetTag("send event from go mysql time ", time.Now().Unix()) + c.SpanContest = span.Context() + span.Finish() return c, nil case s.err = <-s.ech: return nil, s.err From e76d02d0117ff38b5303544dbc147733f4bf7c15 Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Tue, 4 Jun 2019 16:31:39 +0800 Subject: [PATCH 07/13] add tracing lib --- git | 0 vendor/github.com/not.go/.gitignore | 12 + vendor/github.com/not.go/CODE-OF-CONDUCT.md | 3 + vendor/github.com/not.go/GOVERNANCE.md | 3 + vendor/github.com/not.go/LICENSE | 201 ++++++++++++++++ vendor/github.com/not.go/MAINTAINERS.md | 10 + vendor/github.com/not.go/README.md | 215 ++++++++++++++++++ .../not.go/examples/publish/main.go | 88 +++++++ .../github.com/not.go/examples/reply/main.go | 106 +++++++++ .../not.go/examples/request/main.go | 97 ++++++++ .../not.go/examples/subscribe/main.go | 107 +++++++++ vendor/github.com/not.go/images/PubSub.jpg | Bin 0 -> 76584 bytes .../github.com/not.go/images/RequestReply.jpg | Bin 0 -> 72165 bytes vendor/github.com/not.go/not.go | 107 +++++++++ .../github.com/not.go/scripts/start_jaeger.sh | 13 ++ vendor/github.com/opentracing/opentracing-go | 1 + vendor/github.com/uber/jaeger-client-go | 1 + vendor/github.com/uber/jaeger-lib | 1 + 18 files changed, 965 insertions(+) create mode 100644 git create mode 100644 vendor/github.com/not.go/.gitignore create mode 100644 vendor/github.com/not.go/CODE-OF-CONDUCT.md create mode 100644 vendor/github.com/not.go/GOVERNANCE.md create mode 100644 vendor/github.com/not.go/LICENSE create mode 100644 vendor/github.com/not.go/MAINTAINERS.md create mode 100644 vendor/github.com/not.go/README.md create mode 100644 vendor/github.com/not.go/examples/publish/main.go create mode 100644 vendor/github.com/not.go/examples/reply/main.go create mode 100644 vendor/github.com/not.go/examples/request/main.go create mode 100644 vendor/github.com/not.go/examples/subscribe/main.go create mode 100644 vendor/github.com/not.go/images/PubSub.jpg create mode 100644 vendor/github.com/not.go/images/RequestReply.jpg create mode 100644 vendor/github.com/not.go/not.go create mode 100644 vendor/github.com/not.go/scripts/start_jaeger.sh create mode 160000 vendor/github.com/opentracing/opentracing-go create mode 160000 vendor/github.com/uber/jaeger-client-go create mode 160000 vendor/github.com/uber/jaeger-lib diff --git a/git b/git new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/github.com/not.go/.gitignore b/vendor/github.com/not.go/.gitignore new file mode 100644 index 000000000..f1c181ec9 --- /dev/null +++ b/vendor/github.com/not.go/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/github.com/not.go/CODE-OF-CONDUCT.md b/vendor/github.com/not.go/CODE-OF-CONDUCT.md new file mode 100644 index 000000000..0dfc5eb7f --- /dev/null +++ b/vendor/github.com/not.go/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +# Community Code of Conduct + +NATS follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). diff --git a/vendor/github.com/not.go/GOVERNANCE.md b/vendor/github.com/not.go/GOVERNANCE.md new file mode 100644 index 000000000..609f2ef94 --- /dev/null +++ b/vendor/github.com/not.go/GOVERNANCE.md @@ -0,0 +1,3 @@ +# NATS OpenTracing Governance + +This repository is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md). diff --git a/vendor/github.com/not.go/LICENSE b/vendor/github.com/not.go/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/not.go/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/not.go/MAINTAINERS.md b/vendor/github.com/not.go/MAINTAINERS.md new file mode 100644 index 000000000..687408558 --- /dev/null +++ b/vendor/github.com/not.go/MAINTAINERS.md @@ -0,0 +1,10 @@ +# Maintainers + +Maintainership is on a per project basis. + +### Core-maintainers + - Derek Collison [@derekcollison](https://github.com/derekcollison) + - Colin Sullivan [@ColinSullivan1](https://github.com/ColinSullivan1) + +### Maintainers + - Waldemar Quevedo [@wallyqs](https://github.com/wallyqs) diff --git a/vendor/github.com/not.go/README.md b/vendor/github.com/not.go/README.md new file mode 100644 index 000000000..2a2267c96 --- /dev/null +++ b/vendor/github.com/not.go/README.md @@ -0,0 +1,215 @@ +# OpenTracing with NATS + +Over the years, we've had periodic requests to support distributed tracing in +[NATS](https://nats.io). While distributed tracing is valuable, +philosophically we did not want to add external dependencies to NATS, +internally or via API. Nor did we want to provide guidance that would make +developers work in ways that didn't feel natural or aligned with the tenets +of NATS. We left it to the application developers using NATS. + +[OpenTracing](https://opentracing.io) changes this and offers a way to +implement distributed tracing with NATS that aligns with our goals and +philosophy of simplicity and ease of use and does not require adding +dependencies into NATS. This repository provides a reference to +facilitate the use of OpenTracing with NATS enabled applications. + +## What is OpenTracing + +OpenTracing provides a non-intrusive vendor-neutral API and instrumentation +for distributed tracing, with wide language support. Because each use case +is slightly different, we've decided not to provide specific implementations +at this point. Instead we are providing a reference architecture with +examples demonstrating easy usage of OpenTracing with NATS. In line with +other NATS projects, these canonical examples are provided in +[Go](https://golang.org), but this approach should port smoothly into many +other languages. More language examples will be added soon. + +## How it works + +OpenTracing is actually fairly simple to implement in an applicaton. +A "Trace" is defined, and then sets up "spans" to represent an operation +or event and log information, which is reported to the OpenTracing aggregator +for reporting and display. + +To propogate, span contexts are serialized into a NATS message using the +binary format. We provide a `not.TraceMsg` which will do what is needed to +[inject](https://opentracing.io/docs/overview/inject-extract/) span contexts +into messages and to extract them on the other side. + +Here's how to send a span context over NATS. + +```go +// A NATS OpenTracing Message. +var t not.TraceMsg + +// Setup a span for the operation to publish a message. +pubSpan := tracer.StartSpan("Published Message", ext.SpanKindProducer) +ext.MessageBusDestination.Set(pubSpan, subj) +defer pubSpan.Finish() + +// Inject span context into our traceMsg. +if err := tracer.Inject(pubSpan.Context(), opentracing.Binary, &t); err != nil { + log.Fatalf("%v for Inject.", err) +} + +// Add the payload. +t.Write(msg) + +// Send the message over NATS. +nc.Publish(subj, t.Bytes()) +``` + +Note that the payload is added after injection. This order is a requirement, but +simplifies the API and feels natural. + +Retrieving a span from an inbound message and associating with a new response +span is straightforward as well. + +```go +// Create new TraceMsg from the NATS message. +t := not.NewTraceMsg(msg) + +// Extract the span context from the request message. +sc, err := tracer.Extract(opentracing.Binary, t) +if err != nil { + log.Printf("Extract error: %v", err) +} + +// Setup a span referring to the span context of the incoming NATS message. +replySpan := tracer.StartSpan("Service Responder", ext.SpanKindRPCServer, ext.RPCServerOption(sc)) +ext.MessageBusDestination.Set(replySpan, msg.Subject) +defer replySpan.Finish() + +nc.Publish(msg.Reply, reply) +replySpan.LogEvent(fmt.Sprintf("Response msg: %s", reply)) + +``` + +Check out the [examples](./examples) for additional usage. + +## Setting up an OpenTracing Tracer + +To run the the examples, we setup [Jaeger](https://www.jaegertracing.io/) +as the OpenTracing tracer with its convenient "all-in-one" docker image. +Jaeger is a CNCF open source, end-to-end distributed tracing project. + +```bash +docker run -d --name jaeger \ + -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \ + -p 5775:5775/udp \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 14268:14268 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.9 +``` + +See Jaeger's [getting started](https://www.jaegertracing.io/docs/1.9/getting-started/) +documentation for more information. + +## Request/Reply Examples + +* [Requestor Example](./examples/request/main.go) +* [Replier Example](./examples/reply/main.go) + +Open two terminals, in one terminal go to the reply example directory +and run: + +```bash +./reply foo "here's some help" +``` + +In the other terminal, go to the request example direcory and run: + +```bash +./request foo help +``` + +### Request Output + +```text +Initializing logging reporter +Published [foo] : 'help' +Received [_INBOX.4fkjE3Kld4s26FoJxnfxNy.dyOD78y3] : 'here's some help' +Reporting span 3007527f6bf0a38e:3007527f6bf0a38e:0:1 +``` + +### Reply Output + +```text +Initializing logging reporter +Listening on [foo] +Received request: help +Reporting span 3007527f6bf0a38e:66628b457927103d:3007527f6bf0a38e:1 +``` + +### Viewing the Request/Reply output in the Jaeger UI + +Navigate with a browser to http://localhost:16686. Find the _NATS Requestor_ +service in the services list and click the _Find Traces_ button. Click on +the _NATS Requestor_ service and you will see a screen similar to the following: + +![Jaeger UI Request Reply](./images/RequestReply.jpg) + +You can see the entire span of the request and the associated replier span. + +## Publish/Subscribe Examples + +* [Publisher Example](./examples/publish/main.go) +* [Subscriber Example](./examples/subscribe/main.go) + +Open three terminals, in the first two terminals go to the subscribe example +directory and run: + +```bash +go build +./subscribe foo +``` + +and in the second terminal: + +```bash +./subscribe foo +``` + +And finally in the third terminal go to the publish example directory: + +```bash +go build +./publish foo hello +``` + +Navigate with a browser to http://localhost:16686. Find the _NATS Publisher_ +service in the services list and click the _Find Traces_ button. Click on the +_NATS Publisher_ service and you will see a screen to the following: + +![Jaeger UI Publish Subscribe](./images/PubSub.jpg) + +You can see the publish span and the two associated subscriber spans. The gap +the middle includes the NATS client library publishing the message, the NATS server +routing and fanning out the message, and the subscriber NATS clients receiving the +messages and passing them to application code where the subscriber span is reported. + +### Subscriber Output + +```text +Initializing logging reporter +Listening on [foo] +Received msg: "hello" +Reporting span 2b78c114fc32bcad:132b0c35588f3c16:2b78c114fc32bcad:1 +``` + +### Publisher Output + +```text +Initializing logging reporter +Published [foo] : 'hello' +Reporting span 2b78c114fc32bcad:2b78c114fc32bcad:0:1 +``` + +## Our sponsor for this project + +Many thanks to [MasterCard](http://mastercard.com) for sponsoring this project. +We appreciate MasterCard's support of NATS, CNCF, and the OSS community. \ No newline at end of file diff --git a/vendor/github.com/not.go/examples/publish/main.go b/vendor/github.com/not.go/examples/publish/main.go new file mode 100644 index 000000000..2d22154b6 --- /dev/null +++ b/vendor/github.com/not.go/examples/publish/main.go @@ -0,0 +1,88 @@ +// Copyright 2019 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "log" + + "github.com/nats-io/go-nats" + "github.com/nats-io/not.go" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" +) + +func usage() { + log.Fatalf("Usage: publish [-s server] [-creds file] ") +} + +func main() { + var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") + var userCreds = flag.String("creds", "", "User Credentials File") + + log.SetFlags(0) + flag.Usage = usage + flag.Parse() + + args := flag.Args() + if len(args) != 2 { + usage() + } + + tracer, closer := not.InitTracing("NATS Publisher") + opentracing.SetGlobalTracer(tracer) + defer closer.Close() + + // Connect Options. + opts := []nats.Option{nats.Name("NATS Sample Tracing Publisher")} + + // Use UserCredentials. + if *userCreds != "" { + opts = append(opts, nats.UserCredentials(*userCreds)) + } + + // Connect to NATS. + nc, err := nats.Connect(*urls, opts...) + if err != nil { + log.Fatal(err) + } + defer nc.Close() + + subj, msg := args[0], []byte(args[1]) + + // A NATS OpenTracing Message. + var t not.TraceMsg + + // Setup a span for the operation to publish a message. + pubSpan := tracer.StartSpan("Published Message", ext.SpanKindProducer) + ext.MessageBusDestination.Set(pubSpan, subj) + defer pubSpan.Finish() + + // Inject span context into our traceMsg. + if err := tracer.Inject(pubSpan.Context(), opentracing.Binary, &t); err != nil { + log.Fatalf("%v for Inject.", err) + } + + // Add the payload. + t.Write(msg) + + // Send the message over NATS. + nc.Publish(subj, t.Bytes()) + + if err := nc.LastError(); err != nil { + log.Fatal(err) + } else { + log.Printf("Published [%s] : '%s'\n", subj, msg) + } +} diff --git a/vendor/github.com/not.go/examples/reply/main.go b/vendor/github.com/not.go/examples/reply/main.go new file mode 100644 index 000000000..9dd28d9ef --- /dev/null +++ b/vendor/github.com/not.go/examples/reply/main.go @@ -0,0 +1,106 @@ +// Copyright 2019 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "fmt" + "log" + "sync" + + "github.com/nats-io/go-nats" + "github.com/nats-io/not.go" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" +) + +func usage() { + log.Fatalf("Usage: reply [-s server] [-creds file] [-t] ") +} + +func main() { + var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") + var userCreds = flag.String("creds", "", "User Credentials File") + var showTime = flag.Bool("t", false, "Display timestamps") + var numMsgs = flag.Int("n", 1, "Exit after N msgs processed.") + + log.SetFlags(0) + flag.Usage = usage + flag.Parse() + + args := flag.Args() + if len(args) < 2 { + usage() + } + + tracer, closer := not.InitTracing("NATS Responder") + opentracing.SetGlobalTracer(tracer) + defer closer.Close() + + // Connect Options. + opts := []nats.Option{nats.Name("NATS Sample Responder")} + opts = not.SetupConnOptions(tracer, opts) + + // Use UserCredentials + if *userCreds != "" { + opts = append(opts, nats.UserCredentials(*userCreds)) + } + // Connect to NATS + nc, err := nats.Connect(*urls, opts...) + if err != nil { + log.Fatal(err) + } + + wg := sync.WaitGroup{} + wg.Add(*numMsgs) + + subj, reply := args[0], []byte(args[1]) + + nc.Subscribe(subj, func(msg *nats.Msg) { + defer wg.Done() + + // Create new TraceMsg from the NATS message. + t := not.NewTraceMsg(msg) + + // Extract the span context from the request message. + sc, err := tracer.Extract(opentracing.Binary, t) + if err != nil { + log.Printf("Extract error: %v", err) + } + + // Setup a span referring to the span context of the incoming NATS message. + replySpan := tracer.StartSpan("Service Responder", ext.SpanKindRPCServer, ext.RPCServerOption(sc)) + ext.MessageBusDestination.Set(replySpan, msg.Subject) + defer replySpan.Finish() + + log.Printf("Received request: %s", t) + + if err := nc.Publish(msg.Reply, reply); err != nil { + replySpan.LogEvent(fmt.Sprintf("error: %v", err)) + } + + replySpan.LogEvent(fmt.Sprintf("Response msg: %s", reply)) + }) + + if err := nc.LastError(); err != nil { + log.Fatal(err) + } + + log.Printf("Listening on [%s]", subj) + if *showTime { + log.SetFlags(log.LstdFlags) + } + + wg.Wait() +} diff --git a/vendor/github.com/not.go/examples/request/main.go b/vendor/github.com/not.go/examples/request/main.go new file mode 100644 index 000000000..b6f799a39 --- /dev/null +++ b/vendor/github.com/not.go/examples/request/main.go @@ -0,0 +1,97 @@ +// Copyright 2019 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "log" + "time" + + "github.com/nats-io/go-nats" + "github.com/nats-io/not.go" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" +) + +func usage() { + log.Fatalf("Usage: request [-s server] [-creds file] ") +} + +func main() { + var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") + var userCreds = flag.String("creds", "", "User Credentials File") + + log.SetFlags(0) + flag.Usage = usage + flag.Parse() + + args := flag.Args() + if len(args) < 2 { + usage() + } + + // Connect Options. + opts := []nats.Option{nats.Name("NATS Sample Requestor")} + + // Use UserCredentials + if *userCreds != "" { + opts = append(opts, nats.UserCredentials(*userCreds)) + } + + tracer, closer := not.InitTracing("NATS Requestor") + opentracing.SetGlobalTracer(tracer) + defer closer.Close() + + // Connect to NATS + nc, err := nats.Connect(*urls, opts...) + if err != nil { + log.Fatal(err) + } + defer nc.Close() + subj, payload := args[0], []byte(args[1]) + + // Setup our request span + reqSpan := tracer.StartSpan("Service Request", ext.SpanKindRPCClient) + ext.MessageBusDestination.Set(reqSpan, subj) + defer reqSpan.Finish() + + // Log our request + reqSpan.LogEvent("Starting request.") + + // A NATS OpenTracing Message. + var t not.TraceMsg + + // Inject the span context into the TraceMsg. + if err := tracer.Inject(reqSpan.Context(), opentracing.Binary, &t); err != nil { + log.Printf("%v for Inject.", err) + } + + // Add the payload. + t.Write(payload) + + // Make the request. + msg, err := nc.Request(subj, t.Bytes(), time.Second) + if err != nil { + if nc.LastError() != nil { + log.Fatalf("%v for request", nc.LastError()) + } + log.Fatalf("%v for request", err) + } else { + log.Printf("Published [%s] : '%s'", subj, payload) + log.Printf("Received [%v] : '%s'", msg.Subject, string(msg.Data)) + } + + // Log that we've completed the request. + reqSpan.LogEvent("Request Complete") +} diff --git a/vendor/github.com/not.go/examples/subscribe/main.go b/vendor/github.com/not.go/examples/subscribe/main.go new file mode 100644 index 000000000..484bb7d4a --- /dev/null +++ b/vendor/github.com/not.go/examples/subscribe/main.go @@ -0,0 +1,107 @@ +// Copyright 2019 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "flag" + "fmt" + "log" + "sync" + + "github.com/nats-io/go-nats" + "github.com/nats-io/not.go" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" +) + +func usage() { + log.Fatalf("Usage: subscribe [-s server] [-creds file] [-t] [-n msgs] ") +} + +func printMsg(m *nats.Msg, i int) { + log.Printf("[#%d] Received on [%s]: '%s'", i, m.Subject, string(m.Data)) +} + +func main() { + var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") + var userCreds = flag.String("creds", "", "User Credentials File") + var showTime = flag.Bool("t", false, "Display timestamps") + var numMsgs = flag.Int("n", 1, "Exit after N msgs received.") + + log.SetFlags(0) + flag.Usage = usage + flag.Parse() + + args := flag.Args() + if len(args) != 1 { + usage() + } + + tracer, closer := not.InitTracing("NATS Subscriber") + opentracing.SetGlobalTracer(tracer) + defer closer.Close() + + // Connect Options. + opts := []nats.Option{nats.Name("NATS Sample Tracing Subscriber")} + opts = not.SetupConnOptions(tracer, opts) + + // Use UserCredentials. + if *userCreds != "" { + opts = append(opts, nats.UserCredentials(*userCreds)) + } + + // Connect to NATS. + nc, err := nats.Connect(*urls, opts...) + if err != nil { + log.Fatal(err) + } + + // Process N messages then exit. + wg := sync.WaitGroup{} + wg.Add(*numMsgs) + + subj := args[0] + + nc.Subscribe(subj, func(msg *nats.Msg) { + defer wg.Done() + + // Create new TraceMsg from normal NATS message. + t := not.NewTraceMsg(msg) + + // Extract the span context. + sc, err := tracer.Extract(opentracing.Binary, t) + if err != nil { + log.Printf("Extract error: %v", err) + } + + // Setup a span referring to the span context of the incoming NATS message. + span := tracer.StartSpan("Received Message", ext.SpanKindConsumer, opentracing.FollowsFrom(sc)) + ext.MessageBusDestination.Set(span, msg.Subject) + defer span.Finish() + + // The rest of t that has not been read is the payload. + fmt.Printf("Received msg: %q\n", t) + }) + + if err := nc.LastError(); err != nil { + log.Fatal(err) + } + + log.Printf("Listening on [%s]", subj) + if *showTime { + log.SetFlags(log.LstdFlags) + } + + wg.Wait() +} diff --git a/vendor/github.com/not.go/images/PubSub.jpg b/vendor/github.com/not.go/images/PubSub.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a54e15f17a70472a6a492ee8ca3d94fd96222b5 GIT binary patch literal 76584 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/not.go/images/RequestReply.jpg b/vendor/github.com/not.go/images/RequestReply.jpg new file mode 100644 index 0000000000000000000000000000000000000000..528838ca4a55214848e4ad554507e10b8fe74f88 GIT binary patch literal 72165 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 Bz(fE5 literal 0 HcmV?d00001 diff --git a/vendor/github.com/not.go/not.go b/vendor/github.com/not.go/not.go new file mode 100644 index 000000000..9d25786c9 --- /dev/null +++ b/vendor/github.com/not.go/not.go @@ -0,0 +1,107 @@ +// Copyright 2019 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package not + +import ( + "bytes" + "fmt" + "io" + "log" + "time" + + "github.com/nats-io/go-nats" + "github.com/opentracing/opentracing-go" + "github.com/uber/jaeger-lib/metrics" + + jaeger "github.com/uber/jaeger-client-go" + jaegercfg "github.com/uber/jaeger-client-go/config" + jaegerlog "github.com/uber/jaeger-client-go/log" +) + +// TraceMsg will be used as an io.Writer and io.Reader for the span's context and +// the payload. The span will have to be written first and read first. +type TraceMsg struct { + bytes.Buffer +} + +// NewTraceMsg creates a trace msg from a NATS message's data payload. +func NewTraceMsg(m *nats.Msg) *TraceMsg { + b := bytes.NewBuffer(m.Data) + return &TraceMsg{*b} +} + +// InitTracing handles the common tracing setup functionality, and keeps +// implementation specific (Jaeger) configuration here. +func InitTracing(serviceName string) (opentracing.Tracer, io.Closer) { + // Sample configuration for testing. Use constant sampling to sample every trace + // and enable LogSpan to log every span via configured Logger. + cfg := jaegercfg.Configuration{ + ServiceName: serviceName, + Sampler: &jaegercfg.SamplerConfig{ + Type: jaeger.SamplerTypeConst, + Param: 1, + }, + Reporter: &jaegercfg.ReporterConfig{ + LogSpans: true, + }, + } + + // Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log + // and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics + // frameworks. + jLogger := jaegerlog.StdLogger + jMetricsFactory := metrics.NullFactory + + // Initialize tracer with a logger and a metrics factory + tracer, closer, err := cfg.NewTracer( + jaegercfg.Logger(jLogger), + jaegercfg.Metrics(jMetricsFactory), + ) + if err != nil { + log.Fatalf("couldn't setup tracing: %v", err) + } + return tracer, closer +} + +// SetupConnOptions sets up connection options with a tracer to trace +// salient events. +func SetupConnOptions(tracer opentracing.Tracer, opts []nats.Option) []nats.Option { + totalWait := 10 * time.Minute + reconnectDelay := time.Second + + opts = append(opts, nats.ReconnectWait(reconnectDelay)) + opts = append(opts, nats.MaxReconnects(int(totalWait/reconnectDelay))) + opts = append(opts, nats.DisconnectHandler(func(nc *nats.Conn) { + span := tracer.StartSpan("Disconnect Handler") + s := fmt.Sprintf("Disconnected: will attempt reconnects for %.0fm", totalWait.Minutes()) + span.LogEvent(s) + span.Finish() + log.Printf(s) + })) + opts = append(opts, nats.ReconnectHandler(func(nc *nats.Conn) { + span := tracer.StartSpan("Reconnect Handler") + s := fmt.Sprintf("Reconnected [%s]", nc.ConnectedUrl()) + span.LogEvent(s) + span.Finish() + log.Printf(s) + })) + opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { + span := tracer.StartSpan("Closed Handler") + s := "Exiting, no servers available" + span.LogEvent(s) + span.Finish() + log.Printf(s) + })) + return opts +} diff --git a/vendor/github.com/not.go/scripts/start_jaeger.sh b/vendor/github.com/not.go/scripts/start_jaeger.sh new file mode 100644 index 000000000..bd854c557 --- /dev/null +++ b/vendor/github.com/not.go/scripts/start_jaeger.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +docker run -d --name jaeger \ + -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \ + -p 5775:5775/udp \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 14268:14268 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.9 + diff --git a/vendor/github.com/opentracing/opentracing-go b/vendor/github.com/opentracing/opentracing-go new file mode 160000 index 000000000..659c90643 --- /dev/null +++ b/vendor/github.com/opentracing/opentracing-go @@ -0,0 +1 @@ +Subproject commit 659c90643e714681897ec2521c60567dd21da733 diff --git a/vendor/github.com/uber/jaeger-client-go b/vendor/github.com/uber/jaeger-client-go new file mode 160000 index 000000000..896f2abd3 --- /dev/null +++ b/vendor/github.com/uber/jaeger-client-go @@ -0,0 +1 @@ +Subproject commit 896f2abd37e099bae3eae942250d1a37e4bdce0b diff --git a/vendor/github.com/uber/jaeger-lib b/vendor/github.com/uber/jaeger-lib new file mode 160000 index 000000000..d036253de --- /dev/null +++ b/vendor/github.com/uber/jaeger-lib @@ -0,0 +1 @@ +Subproject commit d036253de8f5b698150d81b922486f1e8e7628ec From be8f6577aedc3ad6a836b1f5043859a0058932ac Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Tue, 18 Jun 2019 14:14:57 +0800 Subject: [PATCH 08/13] add tracing in mts --- internal/client/driver/mysql/applier.go | 27 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index d61229847..e83e4cb41 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -1200,11 +1200,12 @@ func (a *Applier) getTableItem(schema string, table string) *applierTableItem { // buildDMLEventQuery creates a query to operate on the ghost table, based on an intercepted binlog // event entry on the original table. -func (a *Applier) buildDMLEventQuery(dmlEvent binlog.DataEvent, workerIdx int) (query *gosql.Stmt, args []interface{}, rowsDelta int64, err error) { +func (a *Applier) buildDMLEventQuery(dmlEvent binlog.DataEvent, workerIdx int, spanContext opentracing.SpanContext) (query *gosql.Stmt, args []interface{}, rowsDelta int64, err error) { // Large piece of code deleted here. See git annotate. tableItem := dmlEvent.TableItem.(*applierTableItem) var tableColumns = tableItem.columns - + span := opentracing.GlobalTracer().StartSpan("desc buildDMLEventQuery ", opentracing.FollowsFrom(spanContext)) + defer span.Finish() doPrepareIfNil := func(stmts []*gosql.Stmt, query string) (*gosql.Stmt, error) { var err error if stmts[workerIdx] == nil { @@ -1269,14 +1270,20 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt var totalDelta int64 var err error + var spanContext opentracing.SpanContext + var span opentracing.Span if ctx != nil { - spanContext := opentracing.SpanFromContext(ctx).Context() - span := opentracing.GlobalTracer().StartSpan("single sql replace into desc ", opentracing.ChildOf(spanContext)) + spanContext = opentracing.SpanFromContext(ctx).Context() + span = opentracing.GlobalTracer().StartSpan("single sql replace into desc ", opentracing.ChildOf(spanContext)) + span.SetTag("start insert sql ", time.Now().UnixNano()/1e6) defer span.Finish() + } else { - spanContext := binlogEntry.SpanContext - span := opentracing.GlobalTracer().StartSpan("mts sql replace into desc ", opentracing.ChildOf(spanContext)) + spanContext = binlogEntry.SpanContext + span = opentracing.GlobalTracer().StartSpan("mts sql replace into desc ", opentracing.ChildOf(spanContext)) + span.SetTag("start insert sql ", time.Now().UnixNano()/1e6) defer span.Finish() + spanContext = span.Context() } txSid := binlogEntry.Coordinates.GetSid() @@ -1286,6 +1293,7 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt return err } defer func() { + span.SetTag("begin commit sql ", time.Now().UnixNano()/1e6) if err := tx.Commit(); err != nil { a.onError(TaskStateDead, err) } else { @@ -1294,10 +1302,11 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt if a.printTps { atomic.AddUint32(&a.txLastNSeconds, 1) } + span.SetTag("after commit sql ", time.Now().UnixNano()/1e6) dbApplier.DbMutex.Unlock() }() - + span.SetTag("begin trans events to sql ", time.Now().UnixNano()/1e6) for i, event := range binlogEntry.Events { a.logger.Debugf("mysql.applier: ApplyBinlogEvent. gno: %v, event: %v", binlogEntry.Coordinates.GNO, i) @@ -1354,7 +1363,7 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt a.logger.Debugf("mysql.applier: Exec [%s]", event.Query) default: a.logger.Debugf("mysql.applier: ApplyBinlogEvent: a dml event") - stmt, args, rowDelta, err := a.buildDMLEventQuery(event, workerIdx) + stmt, args, rowDelta, err := a.buildDMLEventQuery(event, workerIdx, spanContext) if err != nil { a.logger.Errorf("mysql.applier: Build dml query error: %v", err) return err @@ -1377,7 +1386,7 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt totalDelta += rowDelta } } - + span.SetTag("after trans events to sql ", time.Now().UnixNano()/1e6) a.logger.Debugf("ApplyBinlogEvent. insert gno: %v", binlogEntry.Coordinates.GNO) _, err = dbApplier.PsInsertExecutedGtid.Exec(binlogEntry.Coordinates.SID.Bytes(), binlogEntry.Coordinates.GNO) if err != nil { From 4c560860a71404ec31161f988ba252ad725aec4e Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Thu, 25 Jul 2019 14:04:13 +0800 Subject: [PATCH 09/13] add more annotation for tracing --- cmd/dtle/main.go | 2 +- internal/client/driver/mysql/applier.go | 10 +++++----- internal/client/driver/mysql/binlog/binlog_reader.go | 7 +++---- internal/client/driver/mysql/extractor.go | 4 ++-- vendor/github.com/opentracing/opentracing-go | 2 +- .../siddontang/go-mysql/replication/binlogstreamer.go | 2 +- .../siddontang/go-mysql/replication/binlogsyncer.go | 8 ++++---- vendor/github.com/uber/jaeger-client-go | 2 +- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/cmd/dtle/main.go b/cmd/dtle/main.go index 34da56309..ef1051164 100644 --- a/cmd/dtle/main.go +++ b/cmd/dtle/main.go @@ -41,7 +41,7 @@ func main() { Reporter: &config.ReporterConfig{ LogSpans: true, BufferFlushInterval: 1 * time.Second, - LocalAgentHostPort: "10.186.63.111:5775", // 数据上报地址 + LocalAgentHostPort: "10.186.63.9:5775", // 数据上报地址 }, } diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index e83e4cb41..717b18789 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -579,7 +579,7 @@ func (a *Applier) heterogeneousReplay() { continue } spanContext := binlogEntry.SpanContext - span := opentracing.GlobalTracer().StartSpan("desc dtle get binlogEntry", opentracing.ChildOf(spanContext)) + span := opentracing.GlobalTracer().StartSpan("nat : dest to get data ", opentracing.FollowsFrom(spanContext)) ctx = opentracing.ContextWithSpan(ctx, span) a.logger.Debugf("mysql.applier: a binlogEntry. remaining: %v. gno: %v, lc: %v, seq: %v", len(a.applyDataEntryQueue), binlogEntry.Coordinates.GNO, @@ -1274,13 +1274,13 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt var span opentracing.Span if ctx != nil { spanContext = opentracing.SpanFromContext(ctx).Context() - span = opentracing.GlobalTracer().StartSpan("single sql replace into desc ", opentracing.ChildOf(spanContext)) + span = opentracing.GlobalTracer().StartSpan(" desc single binlogEvent transform to sql ", opentracing.ChildOf(spanContext)) span.SetTag("start insert sql ", time.Now().UnixNano()/1e6) defer span.Finish() } else { spanContext = binlogEntry.SpanContext - span = opentracing.GlobalTracer().StartSpan("mts sql replace into desc ", opentracing.ChildOf(spanContext)) + span = opentracing.GlobalTracer().StartSpan("desc mts binlogEvent transform to sql ", opentracing.ChildOf(spanContext)) span.SetTag("start insert sql ", time.Now().UnixNano()/1e6) defer span.Finish() spanContext = span.Context() @@ -1306,7 +1306,7 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt dbApplier.DbMutex.Unlock() }() - span.SetTag("begin trans events to sql ", time.Now().UnixNano()/1e6) + span.SetTag("begin transform binlogEvent to sql time ", time.Now().UnixNano()/1e6) for i, event := range binlogEntry.Events { a.logger.Debugf("mysql.applier: ApplyBinlogEvent. gno: %v, event: %v", binlogEntry.Coordinates.GNO, i) @@ -1386,7 +1386,7 @@ func (a *Applier) ApplyBinlogEvent(ctx context.Context, workerIdx int, binlogEnt totalDelta += rowDelta } } - span.SetTag("after trans events to sql ", time.Now().UnixNano()/1e6) + span.SetTag("after transform binlogEvent to sql ", time.Now().UnixNano()/1e6) a.logger.Debugf("ApplyBinlogEvent. insert gno: %v", binlogEntry.Coordinates.GNO) _, err = dbApplier.PsInsertExecutedGtid.Exec(binlogEntry.Coordinates.SID.Bytes(), binlogEntry.Coordinates.GNO) if err != nil { diff --git a/internal/client/driver/mysql/binlog/binlog_reader.go b/internal/client/driver/mysql/binlog/binlog_reader.go index fbddcd7e9..b93d20433 100644 --- a/internal/client/driver/mysql/binlog/binlog_reader.go +++ b/internal/client/driver/mysql/binlog/binlog_reader.go @@ -313,8 +313,8 @@ type parseDDLResult struct { func (b *BinlogReader) handleEvent(ev *replication.BinlogEvent, entriesChannel chan<- *BinlogEntry) error { spanContext := ev.SpanContest trace := opentracing.GlobalTracer() - span := trace.StartSpan("inc tx to sql", opentracing.ChildOf(spanContext)) - span.SetTag("time", time.Now().Unix()) + span := trace.StartSpan("incremental binlogEvent translation to sql", opentracing.ChildOf(spanContext)) + span.SetTag("begin to translation", time.Now().Unix()) defer span.Finish() if b.currentCoordinates.SmallerThanOrEquals(&b.LastAppliedRowsEventHint) { b.logger.Debugf("mysql.reader: Skipping handled query at %+v", b.currentCoordinates) @@ -717,14 +717,13 @@ func (b *BinlogReader) DataStreamEvents(entriesChannel chan<- *BinlogEntry) erro } trace := opentracing.GlobalTracer() - ev, err := b.binlogStreamer.GetEvent(context.Background()) if err != nil { b.logger.Errorf("mysql.reader error GetEvent. err: %v", err) return err } spanContext := ev.SpanContest - span := trace.StartSpan("get event from mysql-go ", opentracing.ChildOf(spanContext)) + span := trace.StartSpan("DataStreamEvents() get binlogEvent from mysql-go ", opentracing.FollowsFrom(spanContext)) span.SetTag("time", time.Now().Unix()) ev.SpanContest = span.Context() diff --git a/internal/client/driver/mysql/extractor.go b/internal/client/driver/mysql/extractor.go index 139450aa9..cb67817b0 100644 --- a/internal/client/driver/mysql/extractor.go +++ b/internal/client/driver/mysql/extractor.go @@ -845,7 +845,7 @@ func (e *Extractor) StreamEvents() error { select { case binlogEntry := <-e.dataChannel: spanContext := binlogEntry.SpanContext - span := opentracing.GlobalTracer().StartSpan("span_incr_hete", opentracing.ChildOf(spanContext)) + span := opentracing.GlobalTracer().StartSpan("nat send :begin send binlogEntry from src dtle to desc dtle", opentracing.ChildOf(spanContext)) span.SetTag("time", time.Now().Unix()) ctx = opentracing.ContextWithSpan(ctx, span) //span.SetTag("timetag", time.Now().Unix()) @@ -1061,7 +1061,7 @@ func (e *Extractor) publish(ctx context.Context, subject, gtid string, txMsg []b spanctx = parent.Context() } - span := tracer.StartSpan("Nast Publish Message", ext.SpanKindProducer, opentracing.ChildOf(spanctx)) + span := tracer.StartSpan("nat: src publish() to send data ", ext.SpanKindProducer, opentracing.ChildOf(spanctx)) ext.MessageBusDestination.Set(span, subject) diff --git a/vendor/github.com/opentracing/opentracing-go b/vendor/github.com/opentracing/opentracing-go index 659c90643..135aa78c6 160000 --- a/vendor/github.com/opentracing/opentracing-go +++ b/vendor/github.com/opentracing/opentracing-go @@ -1 +1 @@ -Subproject commit 659c90643e714681897ec2521c60567dd21da733 +Subproject commit 135aa78c6f95b4a199daf2f0470d231136cbbd0c diff --git a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go index 25c70d586..22243eedc 100644 --- a/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go +++ b/vendor/github.com/siddontang/go-mysql/replication/binlogstreamer.go @@ -31,7 +31,7 @@ func (s *BinlogStreamer) GetEvent(ctx context.Context) (*BinlogEvent, error) { select { case c := <-s.ch: - span := opentracing.StartSpan("send event from go mysql", opentracing.ChildOf(c.SpanContest)) + span := opentracing.StartSpan("send binlogEvent from go-mysql", opentracing.FollowsFrom(c.SpanContest)) span.SetTag("send event from go mysql time ", time.Now().Unix()) c.SpanContest = span.Context() span.Finish() diff --git a/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go b/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go index 2d831ec79..dc03074d5 100644 --- a/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go +++ b/vendor/github.com/siddontang/go-mysql/replication/binlogsyncer.go @@ -619,10 +619,10 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) { }() for { - span := opentracing.StartSpan(" get inc tx from ReadPacket()") - span.SetTag("begin to get data time ", time.Now().Unix()) + span := opentracing.StartSpan("data source: get incremental data from ReadPacket()") + span.SetTag("before get incremental data time:", time.Now().Unix()) data, err := b.c.ReadPacket() - span.SetTag("after get data time ", time.Now().Unix()) + span.SetTag("after get incremental data time:", time.Now().Unix()) if err != nil { log.Error(err) @@ -695,7 +695,7 @@ func (b *BinlogSyncer) onStream(s *BinlogStreamer) { func (b *BinlogSyncer) parseEvent(spanContext opentracing.SpanContext, s *BinlogStreamer, data []byte) error { //skip OK byte, 0x00 data = data[1:] - span := opentracing.GlobalTracer().StartSpan(" inc tx to BinlogEvent", opentracing.ChildOf(spanContext)) + span := opentracing.GlobalTracer().StartSpan(" incremental data are conversion to BinlogEvent", opentracing.ChildOf(spanContext)) span.SetTag("time", time.Now().Unix()) defer span.Finish() needACK := false diff --git a/vendor/github.com/uber/jaeger-client-go b/vendor/github.com/uber/jaeger-client-go index 896f2abd3..402bec9e6 160000 --- a/vendor/github.com/uber/jaeger-client-go +++ b/vendor/github.com/uber/jaeger-client-go @@ -1 +1 @@ -Subproject commit 896f2abd37e099bae3eae942250d1a37e4bdce0b +Subproject commit 402bec9e6ead856e972bfa449c9c5d6b8965aef1 From cb893e0a41577eae33f9be316ef574eb5be4016d Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Wed, 31 Jul 2019 18:00:53 +0800 Subject: [PATCH 10/13] add tracing config --- agent/agent.go | 3 +- agent/command.go | 71 +++++++++++++++++++++++-- agent/config.go | 31 +++++++---- agent/config_parse.go | 2 + cmd/dtle/main.go | 33 +----------- internal/client/driver/mysql/applier.go | 14 ++--- 6 files changed, 98 insertions(+), 56 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 956235caa..1f95c1b36 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -15,13 +15,12 @@ import ( "sync/atomic" "time" - "github.com/hashicorp/memberlist" - ucli "github.com/actiontech/dtle/internal/client" uconf "github.com/actiontech/dtle/internal/config" ulog "github.com/actiontech/dtle/internal/logger" umodel "github.com/actiontech/dtle/internal/models" usrv "github.com/actiontech/dtle/internal/server" + "github.com/hashicorp/memberlist" ) // Agent is a long running daemon that is used to run both diff --git a/agent/command.go b/agent/command.go index 52c58c4c6..e806a4e61 100644 --- a/agent/command.go +++ b/agent/command.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "log" + "net/http" "os" "os/signal" "path/filepath" @@ -19,16 +20,17 @@ import ( "strings" "syscall" "time" - "net/http" "github.com/actiontech/dtle/internal/g" + ulog "github.com/actiontech/dtle/internal/logger" "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" "github.com/mitchellh/cli" - - ulog "github.com/actiontech/dtle/internal/logger" + opentracing "github.com/opentracing/opentracing-go" "github.com/rakyll/autopprof" + jaeger "github.com/uber/jaeger-client-go" + jaegercnf "github.com/uber/jaeger-client-go/config" report "github.com/ikarishinjieva/golang-live-coverage-report/pkg" ) @@ -94,6 +96,8 @@ func (c *Command) readConfig() *Config { flags.IntVar(&cmdConfig.CoverageReportPort, "coverage-report-port", 0, "") flags.StringVar(&cmdConfig.CoverageReportRawCodeDir, "coverage-report-raw-code-dir", "/usr/lib/dtle", "") flags.StringVar(&cmdConfig.NodeName, "node", "", "") + flags.StringVar(&cmdConfig.JaegerAgentAddress, "jaeger-agent-address", "", "") + flags.StringVar(&cmdConfig.JaegerAgentPort, "jaeger-agent-port", "", "") if err := flags.Parse(c.args); err != nil { return nil @@ -248,6 +252,37 @@ func (c *Command) setupLoggers(config *Config) (io.Writer, error) { // setupAgent is used to start the agent and various interfaces func (c *Command) setupAgent(config *Config, logOutput io.Writer) error { + /*if config.JaegerAgentAddress != "" && config.JaegerAgentPort != "" { + cfg := jaegercnf.Configuration{ + Sampler: &jaegercnf.SamplerConfig{ + Type: "const", + Param: 1, + }, + ServiceName: "dtle", + Reporter: &jaegercnf.ReporterConfig{ + LogSpans: true, + BufferFlushInterval: 1 * time.Second, + }, + } + sender, err := jaeger.NewUDPTransport(config.JaegerAgentAddress+":"+config.JaegerAgentPort, 0) + if err != nil { + return err + } + + reporter := jaeger.NewRemoteReporter(sender) + // Initialize tracer with a logger and a metrics factory + tracer, closer, err := cfg.NewTracer( + jaegercnf.Reporter(reporter), + ) + + + if err != nil { + return err + } + opentracing.SetGlobalTracer(tracer) + defer closer.Close() + }*/ + c.logger.Printf("Starting Dtle server...") agent, err := NewAgent(config, logOutput, c.logger) if err != nil { @@ -275,6 +310,36 @@ func (c *Command) Run(args []string) int { if config == nil { return 1 } + if config.JaegerAgentAddress != "" && config.JaegerAgentPort != "" { + cfg := jaegercnf.Configuration{ + Sampler: &jaegercnf.SamplerConfig{ + Type: "const", + Param: 1, + }, + ServiceName: "dtle", + Reporter: &jaegercnf.ReporterConfig{ + LogSpans: true, + BufferFlushInterval: 1 * time.Second, + }, + } + sender, err := jaeger.NewUDPTransport(config.JaegerAgentAddress+":"+config.JaegerAgentPort, 0) + if err != nil { + return 1 + } + + reporter := jaeger.NewRemoteReporter(sender) + // Initialize tracer with a logger and a metrics factory + tracer, closer, err := cfg.NewTracer( + jaegercnf.Reporter(reporter), + ) + + /*tracer, closer, err := cfg.NewTracer()*/ + if err != nil { + return 1 + } + opentracing.SetGlobalTracer(tracer) + defer closer.Close() + } // Setup the log outputs logOutput, err := c.setupLoggers(config) diff --git a/agent/config.go b/agent/config.go index a9916911e..d4fe69b60 100644 --- a/agent/config.go +++ b/agent/config.go @@ -134,6 +134,11 @@ type Config struct { // CoverageReportRawCodeDir is the root deploy directory of coverage report raw code CoverageReportRawCodeDir string `mapstructure:"coverage_report_raw_code_dir"` + + //jaegerAgentAddress is jaeger tracing Data reporting address + JaegerAgentAddress string `mapstructure:"jaeger_agent_address"` + //jaegerAgentPort is jaeger tracing Data reporting port + JaegerAgentPort string `mapstructure:"jaeger_agent_port"` } // ClientConfig is configuration specific to the client mode @@ -252,15 +257,17 @@ type Node struct { // DefaultConfig is a the baseline configuration for Udup func DefaultConfig() *Config { return &Config{ - LogLevel: "INFO", - LogFile: "/var/log/dtle/dtle.log", - LogToStdout: false, - PprofSwitch: false, - PprofTime: 0, - PidFile: "/var/run/dtle/dtle.pid", - Region: "global", - Datacenter: "dc1", - BindAddr: "0.0.0.0", + LogLevel: "INFO", + LogFile: "/var/log/dtle/dtle.log", + LogToStdout: false, + PprofSwitch: false, + PprofTime: 0, + PidFile: "/var/run/dtle/dtle.pid", + Region: "global", + Datacenter: "dc1", + BindAddr: "0.0.0.0", + JaegerAgentAddress: "", + JaegerAgentPort: "", Ports: &Ports{ HTTP: 8190, RPC: 8191, @@ -376,6 +383,12 @@ func (c *Config) Merge(b *Config) *Config { if b.LeaveOnTerm { result.LeaveOnTerm = true } + if b.JaegerAgentAddress != "" { + result.JaegerAgentAddress = b.JaegerAgentAddress + } + if b.JaegerAgentPort != "" { + result.JaegerAgentPort = b.JaegerAgentPort + } // Apply the metric config if result.Metric == nil && b.Metric != nil { diff --git a/agent/config_parse.go b/agent/config_parse.go index 30c1db64e..9d7f21248 100644 --- a/agent/config_parse.go +++ b/agent/config_parse.go @@ -104,6 +104,8 @@ func parseConfig(result *Config, list *ast.ObjectList) error { "consul", "http_api_response_headers", "dtle_schema_name", + "jaeger_agent_address", + "jaeger_agent_port", } if err := checkHCLKeys(list, valid); err != nil { return multierror.Prefix(err, "config:") diff --git a/cmd/dtle/main.go b/cmd/dtle/main.go index ef1051164..a3190fe11 100644 --- a/cmd/dtle/main.go +++ b/cmd/dtle/main.go @@ -10,18 +10,12 @@ import ( "fmt" "io/ioutil" "log" - "os" - - "github.com/mitchellh/cli" - "github.com/opentracing/opentracing-go" - "github.com/uber/jaeger-client-go/config" - _ "net/http/pprof" - - "time" + "os" "github.com/actiontech/dtle/agent" "github.com/actiontech/dtle/cmd/dtle/command" + "github.com/mitchellh/cli" ) // The git commit that was compiled. This will be filled in by the compiler. @@ -32,34 +26,11 @@ var ( ) func main() { - cfg := config.Configuration{ - Sampler: &config.SamplerConfig{ - Type: "const", - Param: 1, - }, - ServiceName: "dtle_server", - Reporter: &config.ReporterConfig{ - LogSpans: true, - BufferFlushInterval: 1 * time.Second, - LocalAgentHostPort: "10.186.63.9:5775", // 数据上报地址 - }, - } - - tracer, closer, err := cfg.NewTracer() - if err != nil { - log.Panic(err) - } - opentracing.SetGlobalTracer(tracer) - defer closer.Close() - os.Exit(realMain()) } func realMain() int { log.SetOutput(ioutil.Discard) - - // Get the command line args. We shortcut "--version" and "-v" to - // just show the version. args := os.Args[1:] for _, arg := range args { if arg == "-v" || arg == "--version" { diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index 717b18789..46274717d 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -579,7 +579,8 @@ func (a *Applier) heterogeneousReplay() { continue } spanContext := binlogEntry.SpanContext - span := opentracing.GlobalTracer().StartSpan("nat : dest to get data ", opentracing.FollowsFrom(spanContext)) + span := opentracing.GlobalTracer().StartSpan("dest use binlogEntry ", opentracing.FollowsFrom(spanContext)) + span.SetTag("1 use binlogEntry : ", time.Now().UnixNano()/1e5) ctx = opentracing.ContextWithSpan(ctx, span) a.logger.Debugf("mysql.applier: a binlogEntry. remaining: %v. gno: %v, lc: %v, seq: %v", len(a.applyDataEntryQueue), binlogEntry.Coordinates.GNO, @@ -589,7 +590,6 @@ func (a *Applier) heterogeneousReplay() { a.logger.Debugf("mysql.applier: skipping a dtle tx. osid: %v", binlogEntry.Coordinates.OSID) continue } - // region TestIfExecuted if a.gtidExecuted == nil { // udup crash recovery or never executed @@ -599,7 +599,6 @@ func (a *Applier) heterogeneousReplay() { return } } - txSid := binlogEntry.Coordinates.GetSid() gtidSetItem, hasSid := a.gtidExecuted[binlogEntry.Coordinates.SID] @@ -613,7 +612,6 @@ func (a *Applier) heterogeneousReplay() { continue } // endregion - // this must be after duplication check var rotated bool if a.currentCoordinates.File == binlogEntry.Coordinates.LogFile { @@ -640,7 +638,6 @@ func (a *Applier) heterogeneousReplay() { newInterval := append(gtidSetItem.Intervals, thisInterval).Normalize() // TODO this is assigned before real execution gtidSetItem.Intervals = newInterval - if binlogEntry.Coordinates.SeqenceNumber == 0 { // MySQL 5.6: non mts err := a.setTableItemForBinlogEntry(binlogEntry) @@ -664,13 +661,11 @@ func (a *Applier) heterogeneousReplay() { a.logger.Warnf("DTLE_BUG: len(a.mtsManager.m) should be 0") } } - // If there are TXs skipped by udup source-side for a.mtsManager.lastEnqueue+1 < binlogEntry.Coordinates.SeqenceNumber { a.mtsManager.lastEnqueue += 1 a.mtsManager.chExecuted <- a.mtsManager.lastEnqueue } - hasDDL := func() bool { for i := range binlogEntry.Events { dmlEvent := &binlogEntry.Events[i] @@ -682,7 +677,6 @@ func (a *Applier) heterogeneousReplay() { } return false }() - // DDL must be executed separatedly if hasDDL || prevDDL { a.logger.Debugf("mysql.applier: gno: %v MTS found DDL(%v,%v). WaitForAllCommitted", @@ -691,7 +685,6 @@ func (a *Applier) heterogeneousReplay() { return // shutdown } } - if hasDDL { prevDDL = true } else { @@ -701,7 +694,6 @@ func (a *Applier) heterogeneousReplay() { if !a.mtsManager.WaitForExecution(binlogEntry) { return // shutdown } - a.logger.Debugf("mysql.applier: a binlogEntry MTS enqueue. gno: %v", binlogEntry.Coordinates.GNO) err = a.setTableItemForBinlogEntry(binlogEntry) if err != nil { @@ -864,7 +856,7 @@ func (a *Applier) initiateStreaming() error { a.logger.Debugf("applier:get data") } // Setup a span referring to the span context of the incoming NATS message. - replySpan := tracer.StartSpan("nast Subscribe ", ext.SpanKindRPCServer, ext.RPCServerOption(spanContext)) + replySpan := tracer.StartSpan("nast : dest to get data ", ext.SpanKindRPCServer, ext.RPCServerOption(spanContext)) ext.MessageBusDestination.Set(replySpan, m.Subject) defer replySpan.Finish() if err := Decode(t.Bytes(), &binlogEntries); err != nil { From e0c7456bfb4fe55b348b8bcd0bac7c9f9ca84b9b Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Thu, 1 Aug 2019 14:11:53 +0800 Subject: [PATCH 11/13] # Conflicts: # internal/client/driver/mysql/extractor.go --- internal/client/driver/mysql/applier.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index 46274717d..1233fce67 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -45,6 +45,7 @@ import ( "github.com/actiontech/dtle/internal/models" "github.com/actiontech/dtle/utils" + "github.com/not.go" "github.com/satori/go.uuid" ) From c0715bd568eb892187c582e185fe6f516e31cdca Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Fri, 2 Aug 2019 10:11:10 +0800 Subject: [PATCH 12/13] # Conflicts: # internal/client/driver/mysql/extractor.go --- internal/client/driver/mysql/applier.go | 17 +++++++++++++++++ .../client/driver/mysql/binlog/binlog_reader.go | 2 -- internal/client/driver/mysql/extractor.go | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index 1233fce67..efb4824b7 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -494,6 +494,23 @@ func (a *Applier) initNatSubClient() (err error) { a.natsConn = sc return nil } +func DecodeDumpEntry(data []byte) (entry *DumpEntry, err error) { + msg, err := snappy.Decode(nil, data) + if err != nil { + return nil, err + } + + entry = &DumpEntry{} + n, err := entry.Unmarshal(msg) + if err != nil { + return nil, err + } + if n != uint64(len(msg)) { + return nil, fmt.Errorf("DumpEntry.Unmarshal: not all consumed. data: %v, consumed: %v", + len(msg), n) + } + return entry, nil +} // Decode func Decode(data []byte, vPtr interface{}) (err error) { diff --git a/internal/client/driver/mysql/binlog/binlog_reader.go b/internal/client/driver/mysql/binlog/binlog_reader.go index b93d20433..5dabdb187 100644 --- a/internal/client/driver/mysql/binlog/binlog_reader.go +++ b/internal/client/driver/mysql/binlog/binlog_reader.go @@ -38,8 +38,6 @@ import ( "github.com/actiontech/dtle/internal/config" "github.com/actiontech/dtle/internal/config/mysql" - "time" - log "github.com/actiontech/dtle/internal/logger" "github.com/actiontech/dtle/internal/models" "github.com/actiontech/dtle/utils" diff --git a/internal/client/driver/mysql/extractor.go b/internal/client/driver/mysql/extractor.go index cb67817b0..00cbbe70d 100644 --- a/internal/client/driver/mysql/extractor.go +++ b/internal/client/driver/mysql/extractor.go @@ -28,6 +28,7 @@ import ( "github.com/golang/snappy" gonats "github.com/nats-io/go-nats" + "github.com/not.go" gomysql "github.com/siddontang/go-mysql/mysql" "os" From 5c8ad42c03f4f22c372aaf47ed3fce7902c531ec Mon Sep 17 00:00:00 2001 From: "lvhailong@actionsky.com" <790493303@qq.com> Date: Fri, 2 Aug 2019 13:57:12 +0800 Subject: [PATCH 13/13] Delete redundant code --- agent/command.go | 30 ------------------------- internal/client/driver/mysql/applier.go | 1 - 2 files changed, 31 deletions(-) diff --git a/agent/command.go b/agent/command.go index e806a4e61..c5a598a11 100644 --- a/agent/command.go +++ b/agent/command.go @@ -252,36 +252,6 @@ func (c *Command) setupLoggers(config *Config) (io.Writer, error) { // setupAgent is used to start the agent and various interfaces func (c *Command) setupAgent(config *Config, logOutput io.Writer) error { - /*if config.JaegerAgentAddress != "" && config.JaegerAgentPort != "" { - cfg := jaegercnf.Configuration{ - Sampler: &jaegercnf.SamplerConfig{ - Type: "const", - Param: 1, - }, - ServiceName: "dtle", - Reporter: &jaegercnf.ReporterConfig{ - LogSpans: true, - BufferFlushInterval: 1 * time.Second, - }, - } - sender, err := jaeger.NewUDPTransport(config.JaegerAgentAddress+":"+config.JaegerAgentPort, 0) - if err != nil { - return err - } - - reporter := jaeger.NewRemoteReporter(sender) - // Initialize tracer with a logger and a metrics factory - tracer, closer, err := cfg.NewTracer( - jaegercnf.Reporter(reporter), - ) - - - if err != nil { - return err - } - opentracing.SetGlobalTracer(tracer) - defer closer.Close() - }*/ c.logger.Printf("Starting Dtle server...") agent, err := NewAgent(config, logOutput, c.logger) diff --git a/internal/client/driver/mysql/applier.go b/internal/client/driver/mysql/applier.go index efb4824b7..4348b08dd 100644 --- a/internal/client/driver/mysql/applier.go +++ b/internal/client/driver/mysql/applier.go @@ -598,7 +598,6 @@ func (a *Applier) heterogeneousReplay() { } spanContext := binlogEntry.SpanContext span := opentracing.GlobalTracer().StartSpan("dest use binlogEntry ", opentracing.FollowsFrom(spanContext)) - span.SetTag("1 use binlogEntry : ", time.Now().UnixNano()/1e5) ctx = opentracing.ContextWithSpan(ctx, span) a.logger.Debugf("mysql.applier: a binlogEntry. remaining: %v. gno: %v, lc: %v, seq: %v", len(a.applyDataEntryQueue), binlogEntry.Coordinates.GNO,