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 }