github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/plancodec/codec.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package plancodec
    15  
    16  import (
    17  	"bytes"
    18  	"encoding/base64"
    19  	"strconv"
    20  	"strings"
    21  	"sync"
    22  
    23  	"github.com/golang/snappy"
    24  	"github.com/whtcorpsinc/errors"
    25  	"github.com/whtcorpsinc/milevadb/ekv"
    26  	"github.com/whtcorpsinc/milevadb/soliton/texttree"
    27  )
    28  
    29  const (
    30  	rootTaskType = "0"
    31  	copTaskType  = "1"
    32  )
    33  
    34  const (
    35  	idSeparator    = "_"
    36  	lineBreaker    = '\n'
    37  	lineBreakerStr = "\n"
    38  	separator      = '\t'
    39  	separatorStr   = "\t"
    40  )
    41  
    42  var causetDecoderPool = sync.Pool{
    43  	New: func() interface{} {
    44  		return &planCausetDecoder{}
    45  	},
    46  }
    47  
    48  // DecodeCauset use to decode the string to plan tree.
    49  func DecodeCauset(planString string) (string, error) {
    50  	if len(planString) == 0 {
    51  		return "", nil
    52  	}
    53  	fidel := causetDecoderPool.Get().(*planCausetDecoder)
    54  	defer causetDecoderPool.Put(fidel)
    55  	fidel.buf.Reset()
    56  	fidel.addHeader = true
    57  	return fidel.decode(planString)
    58  }
    59  
    60  // DecodeNormalizedCauset decodes the string to plan tree.
    61  func DecodeNormalizedCauset(planString string) (string, error) {
    62  	if len(planString) == 0 {
    63  		return "", nil
    64  	}
    65  	fidel := causetDecoderPool.Get().(*planCausetDecoder)
    66  	defer causetDecoderPool.Put(fidel)
    67  	fidel.buf.Reset()
    68  	fidel.addHeader = false
    69  	return fidel.buildCausetTree(planString)
    70  }
    71  
    72  type planCausetDecoder struct {
    73  	buf              bytes.Buffer
    74  	depths           []int
    75  	indents          [][]rune
    76  	planInfos        []*planInfo
    77  	addHeader        bool
    78  	cacheParentIdent map[int]int
    79  }
    80  
    81  type planInfo struct {
    82  	depth  int
    83  	fields []string
    84  }
    85  
    86  func (fidel *planCausetDecoder) decode(planString string) (string, error) {
    87  	str, err := decompress(planString)
    88  	if err != nil {
    89  		return "", err
    90  	}
    91  	return fidel.buildCausetTree(str)
    92  }
    93  
    94  func (fidel *planCausetDecoder) buildCausetTree(planString string) (string, error) {
    95  	nodes := strings.Split(planString, lineBreakerStr)
    96  	if len(fidel.depths) < len(nodes) {
    97  		fidel.depths = make([]int, 0, len(nodes))
    98  		fidel.planInfos = make([]*planInfo, 0, len(nodes))
    99  		fidel.indents = make([][]rune, 0, len(nodes))
   100  	}
   101  	fidel.depths = fidel.depths[:0]
   102  	fidel.planInfos = fidel.planInfos[:0]
   103  	for _, node := range nodes {
   104  		p, err := decodeCausetInfo(node)
   105  		if err != nil {
   106  			return "", err
   107  		}
   108  		if p == nil {
   109  			continue
   110  		}
   111  		fidel.planInfos = append(fidel.planInfos, p)
   112  		fidel.depths = append(fidel.depths, p.depth)
   113  	}
   114  
   115  	if fidel.addHeader {
   116  		fidel.addCausetHeader()
   117  	}
   118  
   119  	// Calculated indentation of plans.
   120  	fidel.initCausetTreeIndents()
   121  	fidel.cacheParentIdent = make(map[int]int)
   122  	for i := 1; i < len(fidel.depths); i++ {
   123  		parentIndex := fidel.findParentIndex(i)
   124  		fidel.fillIndent(parentIndex, i)
   125  	}
   126  
   127  	// Align the value of plan fields.
   128  	fidel.alignFields()
   129  
   130  	for i, p := range fidel.planInfos {
   131  		if i > 0 {
   132  			fidel.buf.WriteByte(lineBreaker)
   133  		}
   134  		// This is for alignment.
   135  		fidel.buf.WriteByte(separator)
   136  		fidel.buf.WriteString(string(fidel.indents[i]))
   137  		for j := 0; j < len(p.fields); j++ {
   138  			if j > 0 {
   139  				fidel.buf.WriteByte(separator)
   140  			}
   141  			fidel.buf.WriteString(p.fields[j])
   142  		}
   143  	}
   144  	return fidel.buf.String(), nil
   145  }
   146  
   147  func (fidel *planCausetDecoder) addCausetHeader() {
   148  	if len(fidel.planInfos) == 0 {
   149  		return
   150  	}
   151  	header := &planInfo{
   152  		depth:  0,
   153  		fields: []string{"id", "task", "estRows", "operator info", "actRows", "execution info", "memory", "disk"},
   154  	}
   155  	if len(fidel.planInfos[0].fields) < len(header.fields) {
   156  		// plan without runtime information.
   157  		header.fields = header.fields[:len(fidel.planInfos[0].fields)]
   158  	}
   159  	planInfos := make([]*planInfo, 0, len(fidel.planInfos)+1)
   160  	depths := make([]int, 0, len(fidel.planInfos)+1)
   161  	planInfos = append(planInfos, header)
   162  	planInfos = append(planInfos, fidel.planInfos...)
   163  	depths = append(depths, header.depth)
   164  	depths = append(depths, fidel.depths...)
   165  	fidel.planInfos = planInfos
   166  	fidel.depths = depths
   167  }
   168  
   169  func (fidel *planCausetDecoder) initCausetTreeIndents() {
   170  	fidel.indents = fidel.indents[:0]
   171  	for i := 0; i < len(fidel.depths); i++ {
   172  		indent := make([]rune, 2*fidel.depths[i])
   173  		fidel.indents = append(fidel.indents, indent)
   174  		if len(indent) == 0 {
   175  			continue
   176  		}
   177  		for i := 0; i < len(indent)-2; i++ {
   178  			indent[i] = ' '
   179  		}
   180  		indent[len(indent)-2] = texttree.TreeLastNode
   181  		indent[len(indent)-1] = texttree.TreeNodeIdentifier
   182  	}
   183  }
   184  
   185  func (fidel *planCausetDecoder) findParentIndex(childIndex int) int {
   186  	fidel.cacheParentIdent[fidel.depths[childIndex]] = childIndex
   187  	parentDepth := fidel.depths[childIndex] - 1
   188  	if parentIdx, ok := fidel.cacheParentIdent[parentDepth]; ok {
   189  		return parentIdx
   190  	}
   191  	for i := childIndex - 1; i > 0; i-- {
   192  		if fidel.depths[i] == parentDepth {
   193  			fidel.cacheParentIdent[fidel.depths[i]] = i
   194  			return i
   195  		}
   196  	}
   197  	return 0
   198  }
   199  
   200  func (fidel *planCausetDecoder) fillIndent(parentIndex, childIndex int) {
   201  	depth := fidel.depths[childIndex]
   202  	if depth == 0 {
   203  		return
   204  	}
   205  	idx := depth*2 - 2
   206  	for i := childIndex - 1; i > parentIndex; i-- {
   207  		if fidel.indents[i][idx] == texttree.TreeLastNode {
   208  			fidel.indents[i][idx] = texttree.TreeMidbseNode
   209  			break
   210  		}
   211  		fidel.indents[i][idx] = texttree.TreeBody
   212  	}
   213  }
   214  
   215  func (fidel *planCausetDecoder) alignFields() {
   216  	if len(fidel.planInfos) == 0 {
   217  		return
   218  	}
   219  	// Align fields length. Some plan may doesn't have runtime info, need append `` to align with other plan fields.
   220  	maxLen := -1
   221  	for _, p := range fidel.planInfos {
   222  		if len(p.fields) > maxLen {
   223  			maxLen = len(p.fields)
   224  		}
   225  	}
   226  	for _, p := range fidel.planInfos {
   227  		for len(p.fields) < maxLen {
   228  			p.fields = append(p.fields, "")
   229  		}
   230  	}
   231  
   232  	fieldsLen := len(fidel.planInfos[0].fields)
   233  	// Last field no need to align.
   234  	fieldsLen--
   235  	var buf []byte
   236  	for defCausIdx := 0; defCausIdx < fieldsLen; defCausIdx++ {
   237  		maxFieldLen := fidel.getMaxFieldLength(defCausIdx)
   238  		for rowIdx, p := range fidel.planInfos {
   239  			fillLen := maxFieldLen - fidel.getCausetFieldLen(rowIdx, defCausIdx, p)
   240  			for len(buf) < fillLen {
   241  				buf = append(buf, ' ')
   242  			}
   243  			buf = buf[:fillLen]
   244  			p.fields[defCausIdx] += string(buf)
   245  		}
   246  	}
   247  }
   248  
   249  func (fidel *planCausetDecoder) getMaxFieldLength(idx int) int {
   250  	maxLength := -1
   251  	for rowIdx, p := range fidel.planInfos {
   252  		l := fidel.getCausetFieldLen(rowIdx, idx, p)
   253  		if l > maxLength {
   254  			maxLength = l
   255  		}
   256  	}
   257  	return maxLength
   258  }
   259  
   260  func (fidel *planCausetDecoder) getCausetFieldLen(rowIdx, defCausIdx int, p *planInfo) int {
   261  	if defCausIdx == 0 {
   262  		return len(p.fields[0]) + len(fidel.indents[rowIdx])
   263  	}
   264  	return len(p.fields[defCausIdx])
   265  }
   266  
   267  func decodeCausetInfo(str string) (*planInfo, error) {
   268  	values := strings.Split(str, separatorStr)
   269  	if len(values) < 2 {
   270  		return nil, nil
   271  	}
   272  
   273  	p := &planInfo{
   274  		fields: make([]string, 0, len(values)-1),
   275  	}
   276  	for i, v := range values {
   277  		switch i {
   278  		// depth
   279  		case 0:
   280  			depth, err := strconv.Atoi(v)
   281  			if err != nil {
   282  				return nil, errors.Errorf("decode plan: %v, depth: %v, error: %v", str, v, err)
   283  			}
   284  			p.depth = depth
   285  		// plan ID
   286  		case 1:
   287  			ids := strings.Split(v, idSeparator)
   288  			if len(ids) != 1 && len(ids) != 2 {
   289  				return nil, errors.Errorf("decode plan: %v error, invalid plan id: %v", str, v)
   290  			}
   291  			planID, err := strconv.Atoi(ids[0])
   292  			if err != nil {
   293  				return nil, errors.Errorf("decode plan: %v, plan id: %v, error: %v", str, v, err)
   294  			}
   295  			if len(ids) == 1 {
   296  				p.fields = append(p.fields, PhysicalIDToTypeString(planID))
   297  			} else {
   298  				p.fields = append(p.fields, PhysicalIDToTypeString(planID)+idSeparator+ids[1])
   299  			}
   300  		// task type
   301  		case 2:
   302  			task, err := decodeTaskType(v)
   303  			if err != nil {
   304  				return nil, errors.Errorf("decode plan: %v, task type: %v, error: %v", str, v, err)
   305  			}
   306  			p.fields = append(p.fields, task)
   307  		default:
   308  			p.fields = append(p.fields, v)
   309  		}
   310  	}
   311  	return p, nil
   312  }
   313  
   314  // EncodeCausetNode is used to encode the plan to a string.
   315  func EncodeCausetNode(depth, pid int, planType string, rowCount float64,
   316  	taskTypeInfo, explainInfo, actRows, analyzeInfo, memoryInfo, diskInfo string, buf *bytes.Buffer) {
   317  	buf.WriteString(strconv.Itoa(depth))
   318  	buf.WriteByte(separator)
   319  	buf.WriteString(encodeID(planType, pid))
   320  	buf.WriteByte(separator)
   321  	buf.WriteString(taskTypeInfo)
   322  	buf.WriteByte(separator)
   323  	buf.WriteString(strconv.FormatFloat(rowCount, 'f', -1, 64))
   324  	buf.WriteByte(separator)
   325  	buf.WriteString(explainInfo)
   326  	// Check whether has runtime info.
   327  	if len(actRows) > 0 || len(analyzeInfo) > 0 || len(memoryInfo) > 0 || len(diskInfo) > 0 {
   328  		buf.WriteByte(separator)
   329  		buf.WriteString(actRows)
   330  		buf.WriteByte(separator)
   331  		buf.WriteString(analyzeInfo)
   332  		buf.WriteByte(separator)
   333  		buf.WriteString(memoryInfo)
   334  		buf.WriteByte(separator)
   335  		buf.WriteString(diskInfo)
   336  	}
   337  	buf.WriteByte(lineBreaker)
   338  }
   339  
   340  // NormalizeCausetNode is used to normalize the plan to a string.
   341  func NormalizeCausetNode(depth int, planType string, taskTypeInfo string, explainInfo string, buf *bytes.Buffer) {
   342  	buf.WriteString(strconv.Itoa(depth))
   343  	buf.WriteByte(separator)
   344  	planID := TypeStringToPhysicalID(planType)
   345  	buf.WriteString(strconv.Itoa(planID))
   346  	buf.WriteByte(separator)
   347  	buf.WriteString(taskTypeInfo)
   348  	buf.WriteByte(separator)
   349  	buf.WriteString(explainInfo)
   350  	buf.WriteByte(lineBreaker)
   351  }
   352  
   353  func encodeID(planType string, id int) string {
   354  	planID := TypeStringToPhysicalID(planType)
   355  	return strconv.Itoa(planID) + idSeparator + strconv.Itoa(id)
   356  }
   357  
   358  // EncodeTaskType is used to encode task type to a string.
   359  func EncodeTaskType(isRoot bool, storeType ekv.StoreType) string {
   360  	if isRoot {
   361  		return rootTaskType
   362  	}
   363  	return copTaskType + idSeparator + strconv.Itoa((int)(storeType))
   364  }
   365  
   366  // EncodeTaskTypeForNormalize is used to encode task type to a string. Only use for normalize plan.
   367  func EncodeTaskTypeForNormalize(isRoot bool, storeType ekv.StoreType) string {
   368  	if isRoot {
   369  		return rootTaskType
   370  	} else if storeType == ekv.EinsteinDB {
   371  		return copTaskType
   372  	}
   373  	return copTaskType + idSeparator + strconv.Itoa((int)(storeType))
   374  }
   375  
   376  func decodeTaskType(str string) (string, error) {
   377  	segs := strings.Split(str, idSeparator)
   378  	if segs[0] == rootTaskType {
   379  		return "root", nil
   380  	}
   381  	if len(segs) == 1 { // be compatible to `NormalizeCausetNode`, which doesn't encode storeType in task field.
   382  		return "cop", nil
   383  	}
   384  	storeType, err := strconv.Atoi(segs[1])
   385  	if err != nil {
   386  		return "", err
   387  	}
   388  	return "cop[" + ((ekv.StoreType)(storeType)).Name() + "]", nil
   389  }
   390  
   391  // Compress is used to compress the input with zlib.
   392  func Compress(input []byte) string {
   393  	compressBytes := snappy.Encode(nil, input)
   394  	return base64.StdEncoding.EncodeToString(compressBytes)
   395  }
   396  
   397  func decompress(str string) (string, error) {
   398  	decodeBytes, err := base64.StdEncoding.DecodeString(str)
   399  	if err != nil {
   400  		return "", err
   401  	}
   402  
   403  	bs, err := snappy.Decode(nil, decodeBytes)
   404  	if err != nil {
   405  		return "", err
   406  	}
   407  	return string(bs), nil
   408  }