github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/allegrosql/memtable_predicate_extractor.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 embedded 15 16 import ( 17 "bytes" 18 "fmt" 19 "math" 20 "regexp" 21 "sort" 22 "strconv" 23 "strings" 24 "time" 25 26 "github.com/cznic/mathutil" 27 "github.com/whtcorpsinc/BerolinaSQL/allegrosql" 28 "github.com/whtcorpsinc/BerolinaSQL/ast" 29 "github.com/whtcorpsinc/fidelpb/go-fidelpb" 30 "github.com/whtcorpsinc/milevadb/memex" 31 "github.com/whtcorpsinc/milevadb/schemareplicant" 32 "github.com/whtcorpsinc/milevadb/soliton/set" 33 "github.com/whtcorpsinc/milevadb/soliton/stringutil" 34 "github.com/whtcorpsinc/milevadb/stochastikctx" 35 "github.com/whtcorpsinc/milevadb/types" 36 ) 37 38 // MemBlockPredicateExtractor is used to extract some predicates from `WHERE` clause 39 // and push the predicates down to the data retrieving on reading memory causet stage. 40 // 41 // e.g: 42 // SELECT * FROM cluster_config WHERE type='einsteindb' AND instance='192.168.1.9:2379' 43 // We must request all components in the cluster via HTTP API for retrieving 44 // configurations and filter them by `type/instance` columns. 45 // 46 // The purpose of defining a `MemBlockPredicateExtractor` is to optimize this 47 // 1. Define a `ClusterConfigBlockPredicateExtractor` 48 // 2. Extract the `type/instance` columns on the logic optimizing stage and save them via fields. 49 // 3. Passing the extractor to the `ClusterReaderInterDircInterDirc` interlock 50 // 4. InterlockingDirectorate sends requests to the target components instead of all of the components 51 type MemBlockPredicateExtractor interface { 52 // Extracts predicates which can be pushed down and returns the remained predicates 53 Extract(stochastikctx.Context, *memex.Schema, []*types.FieldName, []memex.Expression) (remained []memex.Expression) 54 explainInfo(p *PhysicalMemBlock) string 55 } 56 57 // extractHelper contains some common utililty functions for all extractor. 58 // define an individual struct instead of a bunch of un-exported functions 59 // to avoid polluting the global scope of current package. 60 type extractHelper struct{} 61 62 func (helper extractHelper) extractDefCausInConsExpr(extractDefCauss map[int64]*types.FieldName, expr *memex.ScalarFunction) (string, []types.Causet) { 63 args := expr.GetArgs() 64 col, isDefCaus := args[0].(*memex.DeferredCauset) 65 if !isDefCaus { 66 return "", nil 67 } 68 name, found := extractDefCauss[col.UniqueID] 69 if !found { 70 return "", nil 71 } 72 // All memexs in IN must be a constant 73 // SELECT * FROM t1 WHERE c IN ('1', '2') 74 var results []types.Causet 75 for _, arg := range args[1:] { 76 constant, ok := arg.(*memex.Constant) 77 if !ok || constant.DeferredExpr != nil || constant.ParamMarker != nil { 78 return "", nil 79 } 80 results = append(results, constant.Value) 81 } 82 return name.DefCausName.L, results 83 } 84 85 func (helper extractHelper) extractDefCausBinaryOpConsExpr(extractDefCauss map[int64]*types.FieldName, expr *memex.ScalarFunction) (string, []types.Causet) { 86 args := expr.GetArgs() 87 var col *memex.DeferredCauset 88 var colIdx int 89 // c = 'rhs' 90 // 'lhs' = c 91 for i := 0; i < 2; i++ { 92 var isDefCaus bool 93 col, isDefCaus = args[i].(*memex.DeferredCauset) 94 if isDefCaus { 95 colIdx = i 96 break 97 } 98 } 99 if col == nil { 100 return "", nil 101 } 102 103 name, found := extractDefCauss[col.UniqueID] 104 if !found { 105 return "", nil 106 } 107 // The `lhs/rhs` of EQ memex must be a constant 108 // SELECT * FROM t1 WHERE c='rhs' 109 // SELECT * FROM t1 WHERE 'lhs'=c 110 constant, ok := args[1-colIdx].(*memex.Constant) 111 if !ok || constant.DeferredExpr != nil || constant.ParamMarker != nil { 112 return "", nil 113 } 114 return name.DefCausName.L, []types.Causet{constant.Value} 115 } 116 117 // extract the OR memex, e.g: 118 // SELECT * FROM t1 WHERE c1='a' OR c1='b' OR c1='c' 119 func (helper extractHelper) extractDefCausOrExpr(extractDefCauss map[int64]*types.FieldName, expr *memex.ScalarFunction) (string, []types.Causet) { 120 args := expr.GetArgs() 121 lhs, ok := args[0].(*memex.ScalarFunction) 122 if !ok { 123 return "", nil 124 } 125 rhs, ok := args[1].(*memex.ScalarFunction) 126 if !ok { 127 return "", nil 128 } 129 // Define an inner function to avoid populate the outer scope 130 var extract = func(extractDefCauss map[int64]*types.FieldName, fn *memex.ScalarFunction) (string, []types.Causet) { 131 switch fn.FuncName.L { 132 case ast.EQ: 133 return helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn) 134 case ast.LogicOr: 135 return helper.extractDefCausOrExpr(extractDefCauss, fn) 136 case ast.In: 137 return helper.extractDefCausInConsExpr(extractDefCauss, fn) 138 default: 139 return "", nil 140 } 141 } 142 lhsDefCausName, lhsCausets := extract(extractDefCauss, lhs) 143 if lhsDefCausName == "" { 144 return "", nil 145 } 146 rhsDefCausName, rhsCausets := extract(extractDefCauss, rhs) 147 if lhsDefCausName == rhsDefCausName { 148 return lhsDefCausName, append(lhsCausets, rhsCausets...) 149 } 150 return "", nil 151 } 152 153 // merges `lhs` and `datums` with CNF logic 154 // 1. Returns `datums` set if the `lhs` is an empty set 155 // 2. Returns the intersection of `datums` and `lhs` if the `lhs` is not an empty set 156 func (helper extractHelper) merge(lhs set.StringSet, datums []types.Causet, toLower bool) set.StringSet { 157 tmpNodeTypes := set.NewStringSet() 158 for _, causet := range datums { 159 s, err := causet.ToString() 160 if err != nil { 161 return nil 162 } 163 if toLower { 164 s = strings.ToLower(s) 165 } 166 tmpNodeTypes.Insert(s) 167 } 168 if len(lhs) > 0 { 169 return lhs.Intersection(tmpNodeTypes) 170 } 171 return tmpNodeTypes 172 } 173 174 func (helper extractHelper) extractDefCaus( 175 schemaReplicant *memex.Schema, 176 names []*types.FieldName, 177 predicates []memex.Expression, 178 extractDefCausName string, 179 valueToLower bool, 180 ) ( 181 remained []memex.Expression, 182 skipRequest bool, 183 result set.StringSet, 184 ) { 185 remained = make([]memex.Expression, 0, len(predicates)) 186 result = set.NewStringSet() 187 extractDefCauss := helper.findDeferredCauset(schemaReplicant, names, extractDefCausName) 188 if len(extractDefCauss) == 0 { 189 return predicates, false, result 190 } 191 192 // We should use INTERSECTION of sets because of the predicates is CNF array 193 for _, expr := range predicates { 194 fn, ok := expr.(*memex.ScalarFunction) 195 if !ok { 196 continue 197 } 198 var colName string 199 var datums []types.Causet // the memory of datums should not be reused, they will be put into result. 200 switch fn.FuncName.L { 201 case ast.EQ: 202 colName, datums = helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn) 203 case ast.In: 204 colName, datums = helper.extractDefCausInConsExpr(extractDefCauss, fn) 205 case ast.LogicOr: 206 colName, datums = helper.extractDefCausOrExpr(extractDefCauss, fn) 207 } 208 if colName == extractDefCausName { 209 result = helper.merge(result, datums, valueToLower) 210 skipRequest = len(result) == 0 211 } else { 212 remained = append(remained, expr) 213 } 214 // There are no data if the low-level interlock skip request, so the filter can be droped 215 if skipRequest { 216 remained = remained[:0] 217 break 218 } 219 } 220 return 221 } 222 223 // extracts the string pattern column, e.g: 224 // SELECT * FROM t WHERE c LIKE '%a%' 225 // SELECT * FROM t WHERE c LIKE '%a%' AND c REGEXP '.*xxx.*' 226 // SELECT * FROM t WHERE c LIKE '%a%' OR c REGEXP '.*xxx.*' 227 func (helper extractHelper) extractLikePatternDefCaus( 228 schemaReplicant *memex.Schema, 229 names []*types.FieldName, 230 predicates []memex.Expression, 231 extractDefCausName string, 232 ) ( 233 remained []memex.Expression, 234 patterns []string, 235 ) { 236 remained = make([]memex.Expression, 0, len(predicates)) 237 extractDefCauss := helper.findDeferredCauset(schemaReplicant, names, extractDefCausName) 238 if len(extractDefCauss) == 0 { 239 return predicates, nil 240 } 241 242 // We use a string array to save multiple patterns because the Golang and Rust don't 243 // support perl-like CNF regular memex: (?=expr1)(?=expr2). 244 // e.g: 245 // SELECT * FROM t WHERE c LIKE '%a%' AND c LIKE '%b%' AND c REGEXP 'gc.*[0-9]{10,20}' 246 for _, expr := range predicates { 247 fn, ok := expr.(*memex.ScalarFunction) 248 if !ok { 249 remained = append(remained, expr) 250 continue 251 } 252 253 var canBuildPattern bool 254 var pattern string 255 // We use '|' to combine DNF regular memex: .*a.*|.*b.* 256 // e.g: 257 // SELECT * FROM t WHERE c LIKE '%a%' OR c LIKE '%b%' 258 if fn.FuncName.L == ast.LogicOr { 259 canBuildPattern, pattern = helper.extractOrLikePattern(fn, extractDefCausName, extractDefCauss) 260 } else { 261 canBuildPattern, pattern = helper.extractLikePattern(fn, extractDefCausName, extractDefCauss) 262 } 263 if canBuildPattern { 264 patterns = append(patterns, pattern) 265 } else { 266 remained = append(remained, expr) 267 } 268 } 269 return 270 } 271 272 func (helper extractHelper) extractOrLikePattern( 273 orFunc *memex.ScalarFunction, 274 extractDefCausName string, 275 extractDefCauss map[int64]*types.FieldName, 276 ) ( 277 ok bool, 278 pattern string, 279 ) { 280 predicates := memex.SplitDNFItems(orFunc) 281 if len(predicates) == 0 { 282 return false, "" 283 } 284 285 patternBuilder := make([]string, 0, len(predicates)) 286 for _, predicate := range predicates { 287 fn, ok := predicate.(*memex.ScalarFunction) 288 if !ok { 289 return false, "" 290 } 291 292 ok, partPattern := helper.extractLikePattern(fn, extractDefCausName, extractDefCauss) 293 if !ok { 294 return false, "" 295 } 296 patternBuilder = append(patternBuilder, partPattern) 297 } 298 return true, strings.Join(patternBuilder, "|") 299 } 300 301 func (helper extractHelper) extractLikePattern( 302 fn *memex.ScalarFunction, 303 extractDefCausName string, 304 extractDefCauss map[int64]*types.FieldName, 305 ) ( 306 ok bool, 307 pattern string, 308 ) { 309 var colName string 310 var datums []types.Causet 311 switch fn.FuncName.L { 312 case ast.EQ, ast.Like, ast.Regexp: 313 colName, datums = helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn) 314 } 315 if colName == extractDefCausName { 316 switch fn.FuncName.L { 317 case ast.EQ: 318 return true, "^" + regexp.QuoteMeta(datums[0].GetString()) + "$" 319 case ast.Like: 320 return true, stringutil.CompileLike2Regexp(datums[0].GetString()) 321 case ast.Regexp: 322 return true, datums[0].GetString() 323 default: 324 return false, "" 325 } 326 } else { 327 return false, "" 328 } 329 } 330 331 func (helper extractHelper) findDeferredCauset(schemaReplicant *memex.Schema, names []*types.FieldName, colName string) map[int64]*types.FieldName { 332 extractDefCauss := make(map[int64]*types.FieldName) 333 for i, name := range names { 334 if name.DefCausName.L == colName { 335 extractDefCauss[schemaReplicant.DeferredCausets[i].UniqueID] = name 336 } 337 } 338 return extractDefCauss 339 } 340 341 // getTimeFunctionName is used to get the (time) function name. 342 // For the memex that push down to the interlock, the function name is different with normal compare function, 343 // Then getTimeFunctionName will do a sample function name convert. 344 // Currently, this is used to support query `CLUSTER_SLOW_QUERY` at any time. 345 func (helper extractHelper) getTimeFunctionName(fn *memex.ScalarFunction) string { 346 switch fn.Function.PbCode() { 347 case fidelpb.ScalarFuncSig_GTTime: 348 return ast.GT 349 case fidelpb.ScalarFuncSig_GETime: 350 return ast.GE 351 case fidelpb.ScalarFuncSig_LTTime: 352 return ast.LT 353 case fidelpb.ScalarFuncSig_LETime: 354 return ast.LE 355 case fidelpb.ScalarFuncSig_EQTime: 356 return ast.EQ 357 default: 358 return fn.FuncName.L 359 } 360 } 361 362 // extracts the time range column, e.g: 363 // SELECT * FROM t WHERE time='2020-10-10 10:10:10' 364 // SELECT * FROM t WHERE time>'2020-10-10 10:10:10' AND time<'2020-10-11 10:10:10' 365 func (helper extractHelper) extractTimeRange( 366 ctx stochastikctx.Context, 367 schemaReplicant *memex.Schema, 368 names []*types.FieldName, 369 predicates []memex.Expression, 370 extractDefCausName string, 371 timezone *time.Location, 372 ) ( 373 remained []memex.Expression, 374 // unix timestamp in nanoseconds 375 startTime int64, 376 endTime int64, 377 ) { 378 remained = make([]memex.Expression, 0, len(predicates)) 379 extractDefCauss := helper.findDeferredCauset(schemaReplicant, names, extractDefCausName) 380 if len(extractDefCauss) == 0 { 381 return predicates, startTime, endTime 382 } 383 384 for _, expr := range predicates { 385 fn, ok := expr.(*memex.ScalarFunction) 386 if !ok { 387 remained = append(remained, expr) 388 continue 389 } 390 391 var colName string 392 var datums []types.Causet 393 fnName := helper.getTimeFunctionName(fn) 394 switch fnName { 395 case ast.GT, ast.GE, ast.LT, ast.LE, ast.EQ: 396 colName, datums = helper.extractDefCausBinaryOpConsExpr(extractDefCauss, fn) 397 } 398 399 if colName == extractDefCausName { 400 timeType := types.NewFieldType(allegrosql.TypeDatetime) 401 timeType.Decimal = 6 402 timeCauset, err := datums[0].ConvertTo(ctx.GetStochastikVars().StmtCtx, timeType) 403 if err != nil || timeCauset.HoTT() == types.HoTTNull { 404 remained = append(remained, expr) 405 continue 406 } 407 408 mysqlTime := timeCauset.GetMysqlTime() 409 timestamp := time.Date(mysqlTime.Year(), 410 time.Month(mysqlTime.Month()), 411 mysqlTime.Day(), 412 mysqlTime.Hour(), 413 mysqlTime.Minute(), 414 mysqlTime.Second(), 415 mysqlTime.Microsecond()*1000, 416 timezone, 417 ).UnixNano() 418 419 switch fnName { 420 case ast.EQ: 421 startTime = mathutil.MaxInt64(startTime, timestamp) 422 if endTime == 0 { 423 endTime = timestamp 424 } else { 425 endTime = mathutil.MinInt64(endTime, timestamp) 426 } 427 case ast.GT: 428 // FixMe: add 1ms is not absolutely correct here, just because the log search precision is millisecond. 429 startTime = mathutil.MaxInt64(startTime, timestamp+int64(time.Millisecond)) 430 case ast.GE: 431 startTime = mathutil.MaxInt64(startTime, timestamp) 432 case ast.LT: 433 if endTime == 0 { 434 endTime = timestamp - int64(time.Millisecond) 435 } else { 436 endTime = mathutil.MinInt64(endTime, timestamp-int64(time.Millisecond)) 437 } 438 case ast.LE: 439 if endTime == 0 { 440 endTime = timestamp 441 } else { 442 endTime = mathutil.MinInt64(endTime, timestamp) 443 } 444 default: 445 remained = append(remained, expr) 446 } 447 } else { 448 remained = append(remained, expr) 449 } 450 } 451 return 452 } 453 454 func (helper extractHelper) parseQuantiles(quantileSet set.StringSet) []float64 { 455 quantiles := make([]float64, 0, len(quantileSet)) 456 for k := range quantileSet { 457 v, err := strconv.ParseFloat(k, 64) 458 if err != nil { 459 // ignore the parse error won't affect result. 460 continue 461 } 462 quantiles = append(quantiles, v) 463 } 464 sort.Float64s(quantiles) 465 return quantiles 466 } 467 468 func (helper extractHelper) extractDefCauss( 469 schemaReplicant *memex.Schema, 470 names []*types.FieldName, 471 predicates []memex.Expression, 472 excludeDefCauss set.StringSet, 473 valueToLower bool) ([]memex.Expression, bool, map[string]set.StringSet) { 474 defcaus := map[string]set.StringSet{} 475 remained := predicates 476 skipRequest := false 477 // Extract the label columns. 478 for _, name := range names { 479 if excludeDefCauss.Exist(name.DefCausName.L) { 480 continue 481 } 482 var values set.StringSet 483 remained, skipRequest, values = helper.extractDefCaus(schemaReplicant, names, remained, name.DefCausName.L, valueToLower) 484 if skipRequest { 485 return nil, true, nil 486 } 487 if len(values) == 0 { 488 continue 489 } 490 defcaus[name.DefCausName.L] = values 491 } 492 return remained, skipRequest, defcaus 493 } 494 495 func (helper extractHelper) convertToTime(t int64) time.Time { 496 if t == 0 || t == math.MaxInt64 { 497 return time.Now() 498 } 499 return time.Unix(0, t) 500 } 501 502 // ClusterBlockExtractor is used to extract some predicates of cluster causet. 503 type ClusterBlockExtractor struct { 504 extractHelper 505 506 // SkipRequest means the where clause always false, we don't need to request any component 507 SkipRequest bool 508 509 // NodeTypes represents all components types we should send request to. 510 // e.g: 511 // 1. SELECT * FROM cluster_config WHERE type='einsteindb' 512 // 2. SELECT * FROM cluster_config WHERE type in ('einsteindb', 'milevadb') 513 NodeTypes set.StringSet 514 515 // Instances represents all components instances we should send request to. 516 // e.g: 517 // 1. SELECT * FROM cluster_config WHERE instance='192.168.1.7:2379' 518 // 2. SELECT * FROM cluster_config WHERE type in ('192.168.1.7:2379', '192.168.1.9:2379') 519 Instances set.StringSet 520 } 521 522 // Extract implements the MemBlockPredicateExtractor Extract interface 523 func (e *ClusterBlockExtractor) Extract(_ stochastikctx.Context, 524 schemaReplicant *memex.Schema, 525 names []*types.FieldName, 526 predicates []memex.Expression, 527 ) []memex.Expression { 528 remained, typeSkipRequest, nodeTypes := e.extractDefCaus(schemaReplicant, names, predicates, "type", true) 529 remained, addrSkipRequest, instances := e.extractDefCaus(schemaReplicant, names, remained, "instance", false) 530 e.SkipRequest = typeSkipRequest || addrSkipRequest 531 e.NodeTypes = nodeTypes 532 e.Instances = instances 533 return remained 534 } 535 536 func (e *ClusterBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 537 if e.SkipRequest { 538 return "skip_request:true" 539 } 540 r := new(bytes.Buffer) 541 if len(e.NodeTypes) > 0 { 542 r.WriteString(fmt.Sprintf("node_types:[%s], ", extractStringFromStringSet(e.NodeTypes))) 543 } 544 if len(e.Instances) > 0 { 545 r.WriteString(fmt.Sprintf("instances:[%s], ", extractStringFromStringSet(e.Instances))) 546 } 547 // remove the last ", " in the message info 548 s := r.String() 549 if len(s) > 2 { 550 return s[:len(s)-2] 551 } 552 return s 553 } 554 555 // ClusterLogBlockExtractor is used to extract some predicates of `cluster_config` 556 type ClusterLogBlockExtractor struct { 557 extractHelper 558 559 // SkipRequest means the where clause always false, we don't need to request any component 560 SkipRequest bool 561 562 // NodeTypes represents all components types we should send request to. 563 // e.g: 564 // 1. SELECT * FROM cluster_log WHERE type='einsteindb' 565 // 2. SELECT * FROM cluster_log WHERE type in ('einsteindb', 'milevadb') 566 NodeTypes set.StringSet 567 568 // Instances represents all components instances we should send request to. 569 // e.g: 570 // 1. SELECT * FROM cluster_log WHERE instance='192.168.1.7:2379' 571 // 2. SELECT * FROM cluster_log WHERE instance in ('192.168.1.7:2379', '192.168.1.9:2379') 572 Instances set.StringSet 573 574 // StartTime represents the beginning time of log message 575 // e.g: SELECT * FROM cluster_log WHERE time>'2020-10-10 10:10:10.999' 576 StartTime int64 577 // EndTime represents the ending time of log message 578 // e.g: SELECT * FROM cluster_log WHERE time<'2020-10-11 10:10:10.999' 579 EndTime int64 580 // Pattern is used to filter the log message 581 // e.g: 582 // 1. SELECT * FROM cluster_log WHERE message like '%gc%' 583 // 2. SELECT * FROM cluster_log WHERE message regexp '.*' 584 Patterns []string 585 LogLevels set.StringSet 586 } 587 588 // Extract implements the MemBlockPredicateExtractor Extract interface 589 func (e *ClusterLogBlockExtractor) Extract( 590 ctx stochastikctx.Context, 591 schemaReplicant *memex.Schema, 592 names []*types.FieldName, 593 predicates []memex.Expression, 594 ) []memex.Expression { 595 // Extract the `type/instance` columns 596 remained, typeSkipRequest, nodeTypes := e.extractDefCaus(schemaReplicant, names, predicates, "type", true) 597 remained, addrSkipRequest, instances := e.extractDefCaus(schemaReplicant, names, remained, "instance", false) 598 remained, levlSkipRequest, logLevels := e.extractDefCaus(schemaReplicant, names, remained, "level", true) 599 e.SkipRequest = typeSkipRequest || addrSkipRequest || levlSkipRequest 600 e.NodeTypes = nodeTypes 601 e.Instances = instances 602 e.LogLevels = logLevels 603 if e.SkipRequest { 604 return nil 605 } 606 607 remained, startTime, endTime := e.extractTimeRange(ctx, schemaReplicant, names, remained, "time", time.Local) 608 // The time unit for search log is millisecond. 609 startTime = startTime / int64(time.Millisecond) 610 endTime = endTime / int64(time.Millisecond) 611 e.StartTime = startTime 612 e.EndTime = endTime 613 if startTime != 0 && endTime != 0 { 614 e.SkipRequest = startTime > endTime 615 } 616 617 if e.SkipRequest { 618 return nil 619 } 620 621 remained, patterns := e.extractLikePatternDefCaus(schemaReplicant, names, remained, "message") 622 e.Patterns = patterns 623 return remained 624 } 625 626 func (e *ClusterLogBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 627 if e.SkipRequest { 628 return "skip_request: true" 629 } 630 r := new(bytes.Buffer) 631 st, et := e.StartTime, e.EndTime 632 if st > 0 { 633 st := time.Unix(0, st*1e6) 634 r.WriteString(fmt.Sprintf("start_time:%v, ", st.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat))) 635 } 636 if et > 0 { 637 et := time.Unix(0, et*1e6) 638 r.WriteString(fmt.Sprintf("end_time:%v, ", et.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat))) 639 } 640 if len(e.NodeTypes) > 0 { 641 r.WriteString(fmt.Sprintf("node_types:[%s], ", extractStringFromStringSet(e.NodeTypes))) 642 } 643 if len(e.Instances) > 0 { 644 r.WriteString(fmt.Sprintf("instances:[%s], ", extractStringFromStringSet(e.Instances))) 645 } 646 if len(e.LogLevels) > 0 { 647 r.WriteString(fmt.Sprintf("log_levels:[%s], ", extractStringFromStringSet(e.LogLevels))) 648 } 649 650 // remove the last ", " in the message info 651 s := r.String() 652 if len(s) > 2 { 653 return s[:len(s)-2] 654 } 655 return s 656 } 657 658 // MetricBlockExtractor is used to extract some predicates of metrics_schema blocks. 659 type MetricBlockExtractor struct { 660 extractHelper 661 // SkipRequest means the where clause always false, we don't need to request any component 662 SkipRequest bool 663 // StartTime represents the beginning time of metric data. 664 StartTime time.Time 665 // EndTime represents the ending time of metric data. 666 EndTime time.Time 667 // LabelConditions represents the label conditions of metric data. 668 LabelConditions map[string]set.StringSet 669 Quantiles []float64 670 } 671 672 func newMetricBlockExtractor() *MetricBlockExtractor { 673 e := &MetricBlockExtractor{} 674 e.StartTime, e.EndTime = e.getTimeRange(0, 0) 675 return e 676 } 677 678 // Extract implements the MemBlockPredicateExtractor Extract interface 679 func (e *MetricBlockExtractor) Extract( 680 ctx stochastikctx.Context, 681 schemaReplicant *memex.Schema, 682 names []*types.FieldName, 683 predicates []memex.Expression, 684 ) []memex.Expression { 685 // Extract the `quantile` columns 686 remained, skipRequest, quantileSet := e.extractDefCaus(schemaReplicant, names, predicates, "quantile", true) 687 e.Quantiles = e.parseQuantiles(quantileSet) 688 e.SkipRequest = skipRequest 689 if e.SkipRequest { 690 return nil 691 } 692 693 // Extract the `time` columns 694 remained, startTime, endTime := e.extractTimeRange(ctx, schemaReplicant, names, remained, "time", ctx.GetStochastikVars().StmtCtx.TimeZone) 695 e.StartTime, e.EndTime = e.getTimeRange(startTime, endTime) 696 e.SkipRequest = e.StartTime.After(e.EndTime) 697 if e.SkipRequest { 698 return nil 699 } 700 701 excludeDefCauss := set.NewStringSet("quantile", "time", "value") 702 _, skipRequest, extractDefCauss := e.extractDefCauss(schemaReplicant, names, remained, excludeDefCauss, false) 703 e.SkipRequest = skipRequest 704 if e.SkipRequest { 705 return nil 706 } 707 e.LabelConditions = extractDefCauss 708 // For some metric, the metric reader can't use the predicate, so keep all label conditions remained. 709 return remained 710 } 711 712 func (e *MetricBlockExtractor) getTimeRange(start, end int64) (time.Time, time.Time) { 713 const defaultMetricQueryDuration = 10 * time.Minute 714 var startTime, endTime time.Time 715 if start == 0 && end == 0 { 716 endTime = time.Now() 717 return endTime.Add(-defaultMetricQueryDuration), endTime 718 } 719 if start != 0 { 720 startTime = e.convertToTime(start) 721 } 722 if end != 0 { 723 endTime = e.convertToTime(end) 724 } 725 if start == 0 { 726 startTime = endTime.Add(-defaultMetricQueryDuration) 727 } 728 if end == 0 { 729 endTime = startTime.Add(defaultMetricQueryDuration) 730 } 731 return startTime, endTime 732 } 733 734 func (e *MetricBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 735 if e.SkipRequest { 736 return "skip_request: true" 737 } 738 promQL := e.GetMetricBlockPromQL(p.ctx, p.Block.Name.L) 739 startTime, endTime := e.StartTime, e.EndTime 740 step := time.Second * time.Duration(p.ctx.GetStochastikVars().MetricSchemaStep) 741 return fmt.Sprintf("PromQL:%v, start_time:%v, end_time:%v, step:%v", 742 promQL, 743 startTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat), 744 endTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone).Format(MetricBlockTimeFormat), 745 step, 746 ) 747 } 748 749 // GetMetricBlockPromQL uses to get the promQL of metric causet. 750 func (e *MetricBlockExtractor) GetMetricBlockPromQL(sctx stochastikctx.Context, lowerBlockName string) string { 751 quantiles := e.Quantiles 752 def, err := schemareplicant.GetMetricBlockDef(lowerBlockName) 753 if err != nil { 754 return "" 755 } 756 if len(quantiles) == 0 { 757 quantiles = []float64{def.Quantile} 758 } 759 var buf bytes.Buffer 760 for i, quantile := range quantiles { 761 promQL := def.GenPromQL(sctx, e.LabelConditions, quantile) 762 if i > 0 { 763 buf.WriteByte(',') 764 } 765 buf.WriteString(promQL) 766 } 767 return buf.String() 768 } 769 770 // MetricSummaryBlockExtractor is used to extract some predicates of metrics_schema blocks. 771 type MetricSummaryBlockExtractor struct { 772 extractHelper 773 // SkipRequest means the where clause always false, we don't need to request any component 774 SkipRequest bool 775 MetricsNames set.StringSet 776 Quantiles []float64 777 } 778 779 // Extract implements the MemBlockPredicateExtractor Extract interface 780 func (e *MetricSummaryBlockExtractor) Extract( 781 _ stochastikctx.Context, 782 schemaReplicant *memex.Schema, 783 names []*types.FieldName, 784 predicates []memex.Expression, 785 ) (remained []memex.Expression) { 786 remained, quantileSkip, quantiles := e.extractDefCaus(schemaReplicant, names, predicates, "quantile", false) 787 remained, metricsNameSkip, metricsNames := e.extractDefCaus(schemaReplicant, names, predicates, "metrics_name", true) 788 e.SkipRequest = quantileSkip || metricsNameSkip 789 e.Quantiles = e.parseQuantiles(quantiles) 790 e.MetricsNames = metricsNames 791 return remained 792 } 793 794 func (e *MetricSummaryBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 795 return "" 796 } 797 798 // InspectionResultBlockExtractor is used to extract some predicates of `inspection_result` 799 type InspectionResultBlockExtractor struct { 800 extractHelper 801 // SkipInspection means the where clause always false, we don't need to request any component 802 SkipInspection bool 803 // Memrules represents rules applied to, and we should apply all inspection rules if there is no rules specified 804 // e.g: SELECT * FROM inspection_result WHERE rule in ('dbs', 'config') 805 Memrules set.StringSet 806 // Items represents items applied to, and we should apply all inspection item if there is no rules specified 807 // e.g: SELECT * FROM inspection_result WHERE item in ('dbs.lease', 'raftstore.threadpool') 808 Items set.StringSet 809 } 810 811 // Extract implements the MemBlockPredicateExtractor Extract interface 812 func (e *InspectionResultBlockExtractor) Extract( 813 _ stochastikctx.Context, 814 schemaReplicant *memex.Schema, 815 names []*types.FieldName, 816 predicates []memex.Expression, 817 ) (remained []memex.Expression) { 818 // Extract the `rule/item` columns 819 remained, ruleSkip, rules := e.extractDefCaus(schemaReplicant, names, predicates, "rule", true) 820 remained, itemSkip, items := e.extractDefCaus(schemaReplicant, names, remained, "item", true) 821 e.SkipInspection = ruleSkip || itemSkip 822 e.Memrules = rules 823 e.Items = items 824 return remained 825 } 826 827 func (e *InspectionResultBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 828 if e.SkipInspection { 829 return "skip_inspection:true" 830 } 831 s := make([]string, 0, 2) 832 s = append(s, fmt.Sprintf("rules:[%s]", extractStringFromStringSet(e.Memrules))) 833 s = append(s, fmt.Sprintf("items:[%s]", extractStringFromStringSet(e.Items))) 834 return strings.Join(s, ", ") 835 } 836 837 // InspectionSummaryBlockExtractor is used to extract some predicates of `inspection_summary` 838 type InspectionSummaryBlockExtractor struct { 839 extractHelper 840 // SkipInspection means the where clause always false, we don't need to request any component 841 SkipInspection bool 842 // Memrules represents rules applied to, and we should apply all inspection rules if there is no rules specified 843 // e.g: SELECT * FROM inspection_summary WHERE rule in ('dbs', 'config') 844 Memrules set.StringSet 845 MetricNames set.StringSet 846 Quantiles []float64 847 } 848 849 // Extract implements the MemBlockPredicateExtractor Extract interface 850 func (e *InspectionSummaryBlockExtractor) Extract( 851 _ stochastikctx.Context, 852 schemaReplicant *memex.Schema, 853 names []*types.FieldName, 854 predicates []memex.Expression, 855 ) (remained []memex.Expression) { 856 // Extract the `rule` columns 857 _, ruleSkip, rules := e.extractDefCaus(schemaReplicant, names, predicates, "rule", true) 858 // Extract the `metric_name` columns 859 _, metricNameSkip, metricNames := e.extractDefCaus(schemaReplicant, names, predicates, "metrics_name", true) 860 // Extract the `quantile` columns 861 remained, quantileSkip, quantileSet := e.extractDefCaus(schemaReplicant, names, predicates, "quantile", false) 862 e.SkipInspection = ruleSkip || quantileSkip || metricNameSkip 863 e.Memrules = rules 864 e.Quantiles = e.parseQuantiles(quantileSet) 865 e.MetricNames = metricNames 866 return remained 867 } 868 869 func (e *InspectionSummaryBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 870 if e.SkipInspection { 871 return "skip_inspection: true" 872 } 873 874 r := new(bytes.Buffer) 875 if len(e.Memrules) > 0 { 876 r.WriteString(fmt.Sprintf("rules:[%s], ", extractStringFromStringSet(e.Memrules))) 877 } 878 if len(e.MetricNames) > 0 { 879 r.WriteString(fmt.Sprintf("metric_names:[%s], ", extractStringFromStringSet(e.MetricNames))) 880 } 881 if len(e.Quantiles) > 0 { 882 r.WriteString("quantiles:[") 883 for i, quantile := range e.Quantiles { 884 if i > 0 { 885 r.WriteByte(',') 886 } 887 r.WriteString(fmt.Sprintf("%f", quantile)) 888 } 889 r.WriteString("], ") 890 } 891 892 // remove the last ", " in the message info 893 s := r.String() 894 if len(s) > 2 { 895 return s[:len(s)-2] 896 } 897 return s 898 } 899 900 // InspectionMemruleBlockExtractor is used to extract some predicates of `inspection_rules` 901 type InspectionMemruleBlockExtractor struct { 902 extractHelper 903 904 SkipRequest bool 905 Types set.StringSet 906 } 907 908 // Extract implements the MemBlockPredicateExtractor Extract interface 909 func (e *InspectionMemruleBlockExtractor) Extract( 910 _ stochastikctx.Context, 911 schemaReplicant *memex.Schema, 912 names []*types.FieldName, 913 predicates []memex.Expression, 914 ) (remained []memex.Expression) { 915 // Extract the `type` columns 916 remained, tpSkip, tps := e.extractDefCaus(schemaReplicant, names, predicates, "type", true) 917 e.SkipRequest = tpSkip 918 e.Types = tps 919 return remained 920 } 921 922 func (e *InspectionMemruleBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 923 if e.SkipRequest { 924 return "skip_request: true" 925 } 926 927 r := new(bytes.Buffer) 928 if len(e.Types) > 0 { 929 r.WriteString(fmt.Sprintf("node_types:[%s]", extractStringFromStringSet(e.Types))) 930 } 931 return r.String() 932 } 933 934 // SlowQueryExtractor is used to extract some predicates of `slow_query` 935 type SlowQueryExtractor struct { 936 extractHelper 937 938 SkipRequest bool 939 StartTime time.Time 940 EndTime time.Time 941 // Enable is true means the interlock should use the time range to locate the slow-log file that need to be parsed. 942 // Enable is false, means the interlock should keep the behavior compatible with before, which is only parse the 943 // current slow-log file. 944 Enable bool 945 } 946 947 // Extract implements the MemBlockPredicateExtractor Extract interface 948 func (e *SlowQueryExtractor) Extract( 949 ctx stochastikctx.Context, 950 schemaReplicant *memex.Schema, 951 names []*types.FieldName, 952 predicates []memex.Expression, 953 ) []memex.Expression { 954 remained, startTime, endTime := e.extractTimeRange(ctx, schemaReplicant, names, predicates, "time", ctx.GetStochastikVars().StmtCtx.TimeZone) 955 e.setTimeRange(startTime, endTime) 956 e.SkipRequest = e.Enable && e.StartTime.After(e.EndTime) 957 if e.SkipRequest { 958 return nil 959 } 960 return remained 961 } 962 963 func (e *SlowQueryExtractor) setTimeRange(start, end int64) { 964 const defaultSlowQueryDuration = 24 * time.Hour 965 var startTime, endTime time.Time 966 if start == 0 && end == 0 { 967 return 968 } 969 if start != 0 { 970 startTime = e.convertToTime(start) 971 } 972 if end != 0 { 973 endTime = e.convertToTime(end) 974 } 975 if start == 0 { 976 startTime = endTime.Add(-defaultSlowQueryDuration) 977 } 978 if end == 0 { 979 endTime = startTime.Add(defaultSlowQueryDuration) 980 } 981 e.StartTime, e.EndTime = startTime, endTime 982 e.Enable = true 983 } 984 985 // BlockStorageStatsExtractor is used to extract some predicates of `disk_usage`. 986 type BlockStorageStatsExtractor struct { 987 extractHelper 988 // SkipRequest means the where clause always false, we don't need to request any component. 989 SkipRequest bool 990 // BlockSchema represents blockSchema applied to, and we should apply all causet disk usage if there is no schemaReplicant specified. 991 // e.g: SELECT * FROM information_schema.disk_usage WHERE block_schema in ('test', 'information_schema'). 992 BlockSchema set.StringSet 993 // BlockName represents blockName applied to, and we should apply all causet disk usage if there is no causet specified. 994 // e.g: SELECT * FROM information_schema.disk_usage WHERE causet in ('schemata', 'blocks'). 995 BlockName set.StringSet 996 } 997 998 // Extract implements the MemBlockPredicateExtractor Extract interface. 999 func (e *BlockStorageStatsExtractor) Extract( 1000 _ stochastikctx.Context, 1001 schemaReplicant *memex.Schema, 1002 names []*types.FieldName, 1003 predicates []memex.Expression, 1004 ) []memex.Expression { 1005 // Extract the `block_schema` columns. 1006 remained, schemaSkip, blockSchema := e.extractDefCaus(schemaReplicant, names, predicates, "block_schema", true) 1007 // Extract the `block_name` columns. 1008 remained, blockSkip, blockName := e.extractDefCaus(schemaReplicant, names, remained, "block_name", true) 1009 e.SkipRequest = schemaSkip || blockSkip 1010 if e.SkipRequest { 1011 return nil 1012 } 1013 e.BlockSchema = blockSchema 1014 e.BlockName = blockName 1015 return remained 1016 } 1017 1018 func (e *BlockStorageStatsExtractor) explainInfo(p *PhysicalMemBlock) string { 1019 if e.SkipRequest { 1020 return "skip_request: true" 1021 } 1022 1023 r := new(bytes.Buffer) 1024 if len(e.BlockSchema) > 0 { 1025 r.WriteString(fmt.Sprintf("schemaReplicant:[%s]", extractStringFromStringSet(e.BlockSchema))) 1026 } 1027 if r.Len() > 0 && len(e.BlockName) > 0 { 1028 r.WriteString(", ") 1029 } 1030 if len(e.BlockName) > 0 { 1031 r.WriteString(fmt.Sprintf("causet:[%s]", extractStringFromStringSet(e.BlockName))) 1032 } 1033 return r.String() 1034 } 1035 1036 func (e *SlowQueryExtractor) explainInfo(p *PhysicalMemBlock) string { 1037 if e.SkipRequest { 1038 return "skip_request: true" 1039 } 1040 if !e.Enable { 1041 return fmt.Sprintf("only search in the current '%v' file", p.ctx.GetStochastikVars().SlowQueryFile) 1042 } 1043 startTime := e.StartTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone) 1044 endTime := e.EndTime.In(p.ctx.GetStochastikVars().StmtCtx.TimeZone) 1045 return fmt.Sprintf("start_time:%v, end_time:%v", 1046 types.NewTime(types.FromGoTime(startTime), allegrosql.TypeDatetime, types.MaxFsp).String(), 1047 types.NewTime(types.FromGoTime(endTime), allegrosql.TypeDatetime, types.MaxFsp).String()) 1048 } 1049 1050 // TiFlashSystemBlockExtractor is used to extract some predicates of tiflash system causet. 1051 type TiFlashSystemBlockExtractor struct { 1052 extractHelper 1053 1054 // SkipRequest means the where clause always false, we don't need to request any component 1055 SkipRequest bool 1056 // TiFlashInstances represents all tiflash instances we should send request to. 1057 // e.g: 1058 // 1. SELECT * FROM information_schema.<block_name> WHERE tiflash_instance='192.168.1.7:3930' 1059 // 2. SELECT * FROM information_schema.<block_name> WHERE tiflash_instance in ('192.168.1.7:3930', '192.168.1.9:3930') 1060 TiFlashInstances set.StringSet 1061 // MilevaDBDatabases represents milevadbDatabases applied to, and we should apply all milevadb database if there is no database specified. 1062 // e.g: SELECT * FROM information_schema.<block_name> WHERE milevadb_database in ('test', 'test2'). 1063 MilevaDBDatabases string 1064 // MilevaDBBlocks represents milevadbBlocks applied to, and we should apply all milevadb causet if there is no causet specified. 1065 // e.g: SELECT * FROM information_schema.<block_name> WHERE milevadb_block in ('t', 't2'). 1066 MilevaDBBlocks string 1067 } 1068 1069 // Extract implements the MemBlockPredicateExtractor Extract interface 1070 func (e *TiFlashSystemBlockExtractor) Extract(_ stochastikctx.Context, 1071 schemaReplicant *memex.Schema, 1072 names []*types.FieldName, 1073 predicates []memex.Expression, 1074 ) []memex.Expression { 1075 // Extract the `tiflash_instance` columns. 1076 remained, instanceSkip, tiflashInstances := e.extractDefCaus(schemaReplicant, names, predicates, "tiflash_instance", false) 1077 // Extract the `milevadb_database` columns. 1078 remained, databaseSkip, milevadbDatabases := e.extractDefCaus(schemaReplicant, names, remained, "milevadb_database", true) 1079 // Extract the `milevadb_block` columns. 1080 remained, blockSkip, milevadbBlocks := e.extractDefCaus(schemaReplicant, names, remained, "milevadb_block", true) 1081 e.SkipRequest = instanceSkip || databaseSkip || blockSkip 1082 if e.SkipRequest { 1083 return nil 1084 } 1085 e.TiFlashInstances = tiflashInstances 1086 e.MilevaDBDatabases = extractStringFromStringSet(milevadbDatabases) 1087 e.MilevaDBBlocks = extractStringFromStringSet(milevadbBlocks) 1088 return remained 1089 } 1090 1091 func (e *TiFlashSystemBlockExtractor) explainInfo(p *PhysicalMemBlock) string { 1092 if e.SkipRequest { 1093 return "skip_request:true" 1094 } 1095 r := new(bytes.Buffer) 1096 if len(e.TiFlashInstances) > 0 { 1097 r.WriteString(fmt.Sprintf("tiflash_instances:[%s], ", extractStringFromStringSet(e.TiFlashInstances))) 1098 } 1099 if len(e.MilevaDBDatabases) > 0 { 1100 r.WriteString(fmt.Sprintf("milevadb_databases:[%s], ", e.MilevaDBDatabases)) 1101 } 1102 if len(e.MilevaDBBlocks) > 0 { 1103 r.WriteString(fmt.Sprintf("milevadb_blocks:[%s], ", e.MilevaDBBlocks)) 1104 } 1105 // remove the last ", " in the message info 1106 s := r.String() 1107 if len(s) > 2 { 1108 return s[:len(s)-2] 1109 } 1110 return s 1111 }