tidb binary_plan_decode 源码
tidb binary_plan_decode 代码
文件路径:/util/plancodec/binary_plan_decode.go
// Copyright 2022 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 plancodec
import (
"strconv"
"strings"
"github.com/pingcap/tidb/util/memory"
"github.com/pingcap/tidb/util/texttree"
"github.com/pingcap/tipb/go-tipb"
)
// DecodeBinaryPlan decode the binary plan and display it similar to EXPLAIN ANALYZE statement.
func DecodeBinaryPlan(binaryPlan string) (string, error) {
protoBytes, err := decompress(binaryPlan)
if err != nil {
return "", err
}
pb := &tipb.ExplainData{}
err = pb.Unmarshal(protoBytes)
if err != nil {
return "", err
}
if pb.DiscardedDueToTooLong {
return planDiscardedDecoded, nil
}
// 1. decode the protobuf into strings
rows := decodeBinaryOperator(pb.Main, "", true, pb.WithRuntimeStats, nil)
for _, cte := range pb.Ctes {
rows = decodeBinaryOperator(cte, "", true, pb.WithRuntimeStats, rows)
}
if len(rows) == 0 {
return "", nil
}
// 2. calculate the max length of each column and the total length
// Because the text tree part of the "id" column contains characters that consist of multiple bytes, we need the
// lengths calculated in bytes and runes both. Length in bytes is for preallocating memory. Length in runes is
// for padding space to align the content.
runeMaxLens, byteMaxLens := calculateMaxFieldLens(rows, pb.WithRuntimeStats)
singleRowLen := 0
for _, fieldLen := range byteMaxLens {
singleRowLen += fieldLen
// every field begins with "| " and ends with " "
singleRowLen += 3
}
// every row ends with " |\n"
singleRowLen += 3
// length for a row * (row count + 1(for title row))
totalBytes := singleRowLen * (len(rows) + 1)
// there is a "\n" at the beginning
totalBytes++
// 3. format the strings and get the final result
var b strings.Builder
b.Grow(totalBytes)
var titleFields []string
if pb.WithRuntimeStats {
titleFields = fullTitleFields
} else {
titleFields = noRuntimeStatsTitleFields
}
b.WriteString("\n")
for i, str := range titleFields {
b.WriteString("| ")
b.WriteString(str)
if len([]rune(str)) < runeMaxLens[i] {
// append spaces to align the content
b.WriteString(strings.Repeat(" ", runeMaxLens[i]-len([]rune(str))))
}
b.WriteString(" ")
if i == len(titleFields)-1 {
b.WriteString(" |\n")
}
}
for _, row := range rows {
for i, str := range row {
b.WriteString("| ")
b.WriteString(str)
if len([]rune(str)) < runeMaxLens[i] {
// append spaces to align the content
b.WriteString(strings.Repeat(" ", runeMaxLens[i]-len([]rune(str))))
}
b.WriteString(" ")
if i == len(titleFields)-1 {
b.WriteString(" |\n")
}
}
}
return b.String(), nil
}
var (
noRuntimeStatsTitleFields = []string{"id", "estRows", "estCost", "task", "access object", "operator info"}
fullTitleFields = []string{"id", "estRows", "estCost", "actRows", "task", "access object", "execution info", "operator info", "memory", "disk"}
)
func calculateMaxFieldLens(rows [][]string, hasRuntimeStats bool) (runeLens, byteLens []int) {
runeLens = make([]int, len(rows[0]))
byteLens = make([]int, len(rows[0]))
for _, row := range rows {
for i, field := range row {
if runeLens[i] < len([]rune(field)) {
runeLens[i] = len([]rune(field))
}
if byteLens[i] < len(field) {
byteLens[i] = len(field)
}
}
}
var titleFields []string
if hasRuntimeStats {
titleFields = fullTitleFields
} else {
titleFields = noRuntimeStatsTitleFields
}
for i := range byteLens {
if runeLens[i] < len([]rune(titleFields[i])) {
runeLens[i] = len([]rune(titleFields[i]))
}
if byteLens[i] < len(titleFields[i]) {
byteLens[i] = len(titleFields[i])
}
}
return
}
func decodeBinaryOperator(op *tipb.ExplainOperator, indent string, isLastChild, hasRuntimeStats bool, out [][]string) [][]string {
row := make([]string, 0, 10)
// 1. extract the information and turn them into strings for display
explainID := texttree.PrettyIdentifier(op.Name+printDriverSide(op.Labels), indent, isLastChild)
estRows := strconv.FormatFloat(op.EstRows, 'f', 2, 64)
cost := strconv.FormatFloat(op.Cost, 'f', 2, 64)
var actRows, execInfo, memInfo, diskInfo string
if hasRuntimeStats {
actRows = strconv.FormatInt(int64(op.ActRows), 10)
execInfo = op.RootBasicExecInfo
groupExecInfo := strings.Join(op.RootGroupExecInfo, ",")
if len(groupExecInfo) > 0 {
if len(execInfo) > 0 {
execInfo += ", "
}
execInfo += groupExecInfo
}
if len(op.CopExecInfo) > 0 {
if len(execInfo) > 0 {
execInfo += ", "
}
execInfo += op.CopExecInfo
}
if op.MemoryBytes < 0 {
memInfo = "N/A"
} else {
memInfo = memory.FormatBytes(op.MemoryBytes)
}
if op.DiskBytes < 0 {
diskInfo = "N/A"
} else {
diskInfo = memory.FormatBytes(op.DiskBytes)
}
}
task := op.TaskType.String()
if op.TaskType != tipb.TaskType_unknown && op.TaskType != tipb.TaskType_root {
task = task + "[" + op.StoreType.String() + "]"
}
accessObject := printAccessObject(op.AccessObjects)
// 2. append the strings to the slice
row = append(row, explainID, estRows, cost)
if hasRuntimeStats {
row = append(row, actRows)
}
row = append(row, task, accessObject)
if hasRuntimeStats {
row = append(row, execInfo)
}
row = append(row, op.OperatorInfo)
if hasRuntimeStats {
row = append(row, memInfo, diskInfo)
}
out = append(out, row)
// 3. recursively process the children
children := make([]*tipb.ExplainOperator, len(op.Children))
copy(children, op.Children)
if len(children) == 2 &&
len(children[0].Labels) >= 1 &&
children[0].Labels[0] == tipb.OperatorLabel_probeSide &&
len(children[1].Labels) >= 1 &&
children[1].Labels[0] == tipb.OperatorLabel_buildSide {
children[0], children[1] = children[1], children[0]
}
childIndent := texttree.Indent4Child(indent, isLastChild)
for i, child := range children {
out = decodeBinaryOperator(child, childIndent, i == len(children)-1, hasRuntimeStats, out)
}
return out
}
func printDriverSide(labels []tipb.OperatorLabel) string {
strs := make([]string, 0, len(labels))
for _, label := range labels {
switch label {
case tipb.OperatorLabel_empty:
strs = append(strs, "")
case tipb.OperatorLabel_buildSide:
strs = append(strs, "(Build)")
case tipb.OperatorLabel_probeSide:
strs = append(strs, "(Probe)")
case tipb.OperatorLabel_seedPart:
strs = append(strs, "(Seed Part)")
case tipb.OperatorLabel_recursivePart:
strs = append(strs, "(Recursive Part)")
}
}
return strings.Join(strs, "")
}
func printDynamicPartitionObject(ao *tipb.DynamicPartitionAccessObject) string {
if ao == nil {
return ""
}
if ao.AllPartitions {
return "partition:all"
} else if len(ao.Partitions) == 0 {
return "partition:dual"
}
return "partition:" + strings.Join(ao.Partitions, ",")
}
func printAccessObject(pbAccessObjs []*tipb.AccessObject) string {
strs := make([]string, 0, len(pbAccessObjs))
for _, pbAccessObj := range pbAccessObjs {
switch ao := pbAccessObj.AccessObject.(type) {
case *tipb.AccessObject_DynamicPartitionObjects:
if ao == nil || ao.DynamicPartitionObjects == nil {
return ""
}
aos := ao.DynamicPartitionObjects.Objects
if len(aos) == 0 {
return ""
}
// If it only involves one table, just print the partitions.
if len(aos) == 1 {
return printDynamicPartitionObject(aos[0])
}
var b strings.Builder
// If it involves multiple tables, we also need to print the table name.
for i, access := range aos {
if access == nil {
continue
}
if i != 0 {
b.WriteString(", ")
}
b.WriteString(printDynamicPartitionObject(access))
b.WriteString(" of " + access.Table)
}
strs = append(strs, b.String())
case *tipb.AccessObject_ScanObject:
if ao == nil || ao.ScanObject == nil {
return ""
}
scanAO := ao.ScanObject
var b strings.Builder
if len(scanAO.Table) > 0 {
b.WriteString("table:" + scanAO.Table)
}
if len(scanAO.Partitions) > 0 {
b.WriteString(", partition:" + strings.Join(scanAO.Partitions, ","))
}
for _, index := range scanAO.Indexes {
if index.IsClusteredIndex {
b.WriteString(", clustered index:")
} else {
b.WriteString(", index:")
}
b.WriteString(index.Name + "(" + strings.Join(index.Cols, ", ") + ")")
}
strs = append(strs, b.String())
case *tipb.AccessObject_OtherObject:
strs = append(strs, ao.OtherObject)
}
}
return strings.Join(strs, "")
}
相关信息
相关文章
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦