Files
tidb/util/tracing/opt_trace.go
2022-01-26 17:06:45 +08:00

252 lines
8.1 KiB
Go

// Copyright 2021 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,
// 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 tracing
import "fmt"
// PlanTrace indicates for the Plan trace information
type PlanTrace struct {
ID int `json:"id"`
TP string `json:"type"`
Children []*PlanTrace `json:"-"`
ChildrenID []int `json:"children"`
Cost float64 `json:"cost"`
Selected bool `json:"selected"`
ProperType string `json:"property"`
// ExplainInfo should be implemented by each implemented Plan
ExplainInfo string `json:"info"`
}
// LogicalOptimizeTracer indicates the trace for the whole logicalOptimize processing
type LogicalOptimizeTracer struct {
FinalLogicalPlan []*PlanTrace `json:"final"`
Steps []*LogicalRuleOptimizeTracer `json:"steps"`
// curRuleTracer indicates the current rule Tracer during optimize by rule
curRuleTracer *LogicalRuleOptimizeTracer
}
// AppendRuleTracerBeforeRuleOptimize add plan tracer before optimize
func (tracer *LogicalOptimizeTracer) AppendRuleTracerBeforeRuleOptimize(index int, name string, before *PlanTrace) {
ruleTracer := buildLogicalRuleOptimizeTracerBeforeOptimize(index, name, before)
tracer.Steps = append(tracer.Steps, ruleTracer)
tracer.curRuleTracer = ruleTracer
}
// AppendRuleTracerStepToCurrent add rule optimize step to current
func (tracer *LogicalOptimizeTracer) AppendRuleTracerStepToCurrent(id int, tp, reason, action string) {
index := len(tracer.curRuleTracer.Steps)
tracer.curRuleTracer.Steps = append(tracer.curRuleTracer.Steps, LogicalRuleOptimizeTraceStep{
ID: id,
TP: tp,
Reason: reason,
Action: action,
Index: index,
})
}
// RecordFinalLogicalPlan add plan trace after logical optimize
func (tracer *LogicalOptimizeTracer) RecordFinalLogicalPlan(final *PlanTrace) {
tracer.FinalLogicalPlan = toFlattenPlanTrace(final)
tracer.removeUselessStep()
}
// TODO: use a switch to control it
func (tracer *LogicalOptimizeTracer) removeUselessStep() {
newSteps := make([]*LogicalRuleOptimizeTracer, 0)
for _, step := range tracer.Steps {
if len(step.Steps) > 0 {
newSteps = append(newSteps, step)
}
}
tracer.Steps = newSteps
}
// LogicalRuleOptimizeTracer indicates the trace for the LogicalPlan tree before and after
// logical rule optimize
type LogicalRuleOptimizeTracer struct {
Index int `json:"index"`
Before []*PlanTrace `json:"before"`
RuleName string `json:"name"`
Steps []LogicalRuleOptimizeTraceStep `json:"steps"`
}
// buildLogicalRuleOptimizeTracerBeforeOptimize build rule tracer before rule optimize
func buildLogicalRuleOptimizeTracerBeforeOptimize(index int, name string, before *PlanTrace) *LogicalRuleOptimizeTracer {
return &LogicalRuleOptimizeTracer{
Index: index,
Before: toFlattenPlanTrace(before),
RuleName: name,
Steps: make([]LogicalRuleOptimizeTraceStep, 0),
}
}
// LogicalRuleOptimizeTraceStep indicates the trace for the detailed optimize changing in
// logical rule optimize
type LogicalRuleOptimizeTraceStep struct {
Action string `json:"action"`
Reason string `json:"reason"`
ID int `json:"id"`
TP string `json:"type"`
Index int `json:"index"`
}
// toFlattenPlanTrace transform plan into PlanTrace
func toFlattenPlanTrace(root *PlanTrace) []*PlanTrace {
wrapper := &flattenWrapper{flatten: make([]*PlanTrace, 0)}
flattenLogicalPlanTrace(root, wrapper)
return wrapper.flatten
}
type flattenWrapper struct {
flatten []*PlanTrace
}
func flattenLogicalPlanTrace(node *PlanTrace, wrapper *flattenWrapper) {
newNode := &PlanTrace{
ID: node.ID,
TP: node.TP,
ChildrenID: make([]int, 0),
Cost: node.Cost,
ExplainInfo: node.ExplainInfo,
}
if len(node.Children) < 1 {
wrapper.flatten = append(wrapper.flatten, newNode)
return
}
for _, child := range node.Children {
newNode.ChildrenID = append(newNode.ChildrenID, child.ID)
}
for _, child := range node.Children {
flattenLogicalPlanTrace(child, wrapper)
}
wrapper.flatten = append(wrapper.flatten, newNode)
}
// CETraceRecord records an expression and related cardinality estimation result.
type CETraceRecord struct {
TableID int64 `json:"-"`
TableName string `json:"table_name"`
Type string `json:"type"`
Expr string `json:"expr"`
RowCount uint64 `json:"row_count"`
}
// DedupCETrace deduplicate a slice of *CETraceRecord and return the deduplicated slice
func DedupCETrace(records []*CETraceRecord) []*CETraceRecord {
ret := make([]*CETraceRecord, 0, len(records))
exists := make(map[CETraceRecord]struct{}, len(records))
for _, rec := range records {
if _, ok := exists[*rec]; !ok {
ret = append(ret, rec)
exists[*rec] = struct{}{}
}
}
return ret
}
// PhysicalOptimizeTracer indicates the trace for the whole physicalOptimize processing
type PhysicalOptimizeTracer struct {
// final indicates the final physical plan trace
Final []*PlanTrace `json:"final"`
SelectedCandidates []*CandidatePlanTrace `json:"selected_candidates"`
DiscardedCandidates []*CandidatePlanTrace `json:"discarded_candidates"`
// (logical plan) -> physical plan codename -> physical plan
State map[string]map[string]*PlanTrace `json:"-"`
}
// RecordFinalPlanTrace records final physical plan trace
func (tracer *PhysicalOptimizeTracer) RecordFinalPlanTrace(root *PlanTrace) {
tracer.Final = toFlattenPlanTrace(root)
tracer.buildCandidatesInfo()
}
// CandidatePlanTrace indicates info for candidate
type CandidatePlanTrace struct {
*PlanTrace
MappingLogicalPlan string `json:"mapping"`
}
func newCandidatePlanTrace(trace *PlanTrace, logicalPlanKey string, bestKey map[string]struct{}) *CandidatePlanTrace {
selected := false
if _, ok := bestKey[CodecPlanName(trace.TP, trace.ID)]; ok {
selected = true
}
c := &CandidatePlanTrace{
MappingLogicalPlan: logicalPlanKey,
}
c.PlanTrace = trace
c.Selected = selected
for i, child := range c.Children {
if _, ok := bestKey[CodecPlanName(child.TP, child.ID)]; ok {
child.Selected = true
}
c.Children[i] = child
}
return c
}
// buildCandidatesInfo builds candidates info
func (tracer *PhysicalOptimizeTracer) buildCandidatesInfo() {
if tracer == nil || len(tracer.State) < 1 {
return
}
sCandidates := make([]*CandidatePlanTrace, 0)
dCandidates := make([]*CandidatePlanTrace, 0)
bestKeys := map[string]struct{}{}
for _, node := range tracer.Final {
bestKeys[CodecPlanName(node.TP, node.ID)] = struct{}{}
}
for logicalKey, pps := range tracer.State {
for _, pp := range pps {
c := newCandidatePlanTrace(pp, logicalKey, bestKeys)
if c.Selected {
sCandidates = append(sCandidates, c)
} else {
dCandidates = append(dCandidates, c)
}
}
}
tracer.SelectedCandidates = sCandidates
tracer.DiscardedCandidates = dCandidates
}
// CodecPlanName returns tp_id of plan.
func CodecPlanName(tp string, id int) string {
return fmt.Sprintf("%v_%v", tp, id)
}
// OptimizeTracer indicates tracer for optimizer
type OptimizeTracer struct {
// Logical indicates logical plan
Logical *LogicalOptimizeTracer `json:"logical"`
// Physical indicates physical plan
Physical *PhysicalOptimizeTracer `json:"physical"`
// FinalPlan indicates the plan after post optimize
FinalPlan []*PlanTrace `json:"final"`
// IsFastPlan indicates whether the plan is generated by fast plan
IsFastPlan bool `json:"isFastPlan"`
}
// SetFastPlan sets fast plan
func (tracer *OptimizeTracer) SetFastPlan(final *PlanTrace) {
tracer.FinalPlan = toFlattenPlanTrace(final)
tracer.IsFastPlan = true
}
// RecordFinalPlan records plan after post optimize
func (tracer *OptimizeTracer) RecordFinalPlan(final *PlanTrace) {
tracer.FinalPlan = toFlattenPlanTrace(final)
}