You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

244 lines
6.5 KiB

// Copyright 2018 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package executor
import (
"context"
"encoding/json"
"fmt"
"sort"
"time"
"github.com/opentracing/basictracer-go"
"github.com/opentracing/opentracing-go"
"github.com/pingcap/errors"
"github.com/pingcap/parser/ast"
"github.com/pingcap/parser/mysql"
"github.com/pingcap/parser/terror"
"github.com/pingcap/tidb/planner/core"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/logutil"
"github.com/pingcap/tidb/util/sqlexec"
"go.uber.org/zap"
"sourcegraph.com/sourcegraph/appdash"
traceImpl "sourcegraph.com/sourcegraph/appdash/opentracing"
)
// TraceExec represents a root executor of trace query.
type TraceExec struct {
baseExecutor
// CollectedSpans collects all span during execution. Span is appended via
// callback method which passes into tracer implementation.
CollectedSpans []basictracer.RawSpan
// exhausted being true means there is no more result.
exhausted bool
// stmtNode is the real query ast tree and it is used for building real query's plan.
stmtNode ast.StmtNode
// rootTrace represents root span which is father of all other span.
rootTrace opentracing.Span
builder *executorBuilder
format string
}
// Next executes real query and collects span later.
func (e *TraceExec) Next(ctx context.Context, req *chunk.Chunk) error {
req.Reset()
if e.exhausted {
return nil
}
se, ok := e.ctx.(sqlexec.SQLExecutor)
if !ok {
e.exhausted = true
return nil
}
switch e.format {
case core.TraceFormatLog:
return e.nextTraceLog(ctx, se, req)
default:
return e.nextRowJSON(ctx, se, req)
}
}
func (e *TraceExec) nextTraceLog(ctx context.Context, se sqlexec.SQLExecutor, req *chunk.Chunk) error {
recorder := basictracer.NewInMemoryRecorder()
tracer := basictracer.New(recorder)
span := tracer.StartSpan("trace")
ctx = opentracing.ContextWithSpan(ctx, span)
e.executeChild(ctx, se)
span.Finish()
generateLogResult(recorder.GetSpans(), req)
e.exhausted = true
return nil
}
func (e *TraceExec) nextRowJSON(ctx context.Context, se sqlexec.SQLExecutor, req *chunk.Chunk) error {
store := appdash.NewMemoryStore()
tracer := traceImpl.NewTracer(store)
span := tracer.StartSpan("trace")
ctx = opentracing.ContextWithSpan(ctx, span)
e.executeChild(ctx, se)
span.Finish()
traces, err := store.Traces(appdash.TracesOpts{})
if err != nil {
return errors.Trace(err)
}
// Row format.
if e.format != core.TraceFormatJSON {
if len(traces) < 1 {
e.exhausted = true
return nil
}
trace := traces[0]
dfsTree(trace, "", false, req)
e.exhausted = true
return nil
}
// Json format.
data, err := json.Marshal(traces)
if err != nil {
return errors.Trace(err)
}
// Split json data into rows to avoid the max packet size limitation.
const maxRowLen = 4096
for len(data) > maxRowLen {
req.AppendString(0, string(data[:maxRowLen]))
data = data[maxRowLen:]
}
req.AppendString(0, string(data))
e.exhausted = true
return nil
}
func (e *TraceExec) executeChild(ctx context.Context, se sqlexec.SQLExecutor) {
recordSets, err := se.Execute(ctx, e.stmtNode.Text())
if len(recordSets) == 0 {
if err != nil {
var errCode uint16
if te, ok := err.(*terror.Error); ok {
errCode = terror.ToSQLError(te).Code
}
logutil.Eventf(ctx, "execute with error(%d): %s", errCode, err.Error())
} else {
logutil.Eventf(ctx, "execute done, modify row: %d", e.ctx.GetSessionVars().StmtCtx.AffectedRows())
}
}
for _, rs := range recordSets {
drainRecordSet(ctx, e.ctx, rs)
if err = rs.Close(); err != nil {
logutil.Logger(ctx).Error("run trace close result with error", zap.Error(err))
}
}
}
func drainRecordSet(ctx context.Context, sctx sessionctx.Context, rs sqlexec.RecordSet) {
req := rs.NewChunk()
var rowCount int
for {
err := rs.Next(ctx, req)
if err != nil || req.NumRows() == 0 {
if err != nil {
var errCode uint16
if te, ok := err.(*terror.Error); ok {
errCode = terror.ToSQLError(te).Code
}
logutil.Eventf(ctx, "execute with error(%d): %s", errCode, err.Error())
} else {
logutil.Eventf(ctx, "execute done, ReturnRow: %d, ModifyRow: %d", rowCount, sctx.GetSessionVars().StmtCtx.AffectedRows())
}
return
}
rowCount += req.NumRows()
req.Reset()
}
}
func dfsTree(t *appdash.Trace, prefix string, isLast bool, chk *chunk.Chunk) {
var newPrefix, suffix string
if len(prefix) == 0 {
newPrefix = prefix + " "
} else {
if !isLast {
suffix = "├─"
newPrefix = prefix + "│ "
} else {
suffix = "└─"
newPrefix = prefix + " "
}
}
var start time.Time
var duration time.Duration
if e, err := t.TimespanEvent(); err == nil {
start = e.Start()
end := e.End()
duration = end.Sub(start)
}
chk.AppendString(0, prefix+suffix+t.Span.Name())
chk.AppendString(1, start.Format("15:04:05.000000"))
chk.AppendString(2, duration.String())
// Sort events by their start time
sort.Slice(t.Sub, func(i, j int) bool {
var istart, jstart time.Time
if ievent, err := t.Sub[i].TimespanEvent(); err == nil {
istart = ievent.Start()
}
if jevent, err := t.Sub[j].TimespanEvent(); err == nil {
jstart = jevent.Start()
}
return istart.Before(jstart)
})
for i, sp := range t.Sub {
dfsTree(sp, newPrefix, i == (len(t.Sub))-1 /*last element of array*/, chk)
}
}
func generateLogResult(allSpans []basictracer.RawSpan, chk *chunk.Chunk) {
for rIdx := range allSpans {
span := &allSpans[rIdx]
chk.AppendTime(0, types.NewTime(types.FromGoTime(span.Start), mysql.TypeTimestamp, 6))
chk.AppendString(1, "--- start span "+span.Operation+" ----")
chk.AppendString(2, "")
chk.AppendString(3, span.Operation)
var tags string
if len(span.Tags) > 0 {
tags = fmt.Sprintf("%v", span.Tags)
}
for _, l := range span.Logs {
for _, field := range l.Fields {
if field.Key() == logutil.TraceEventKey {
chk.AppendTime(0, types.NewTime(types.FromGoTime(l.Timestamp), mysql.TypeTimestamp, 6))
chk.AppendString(1, field.Value().(string))
chk.AppendString(2, tags)
chk.AppendString(3, span.Operation)
}
}
}
}
}