From 9840c539eaba6e535e98e44ee6fbd85b211d3c3f Mon Sep 17 00:00:00 2001 From: crazycs Date: Mon, 1 Jun 2020 22:08:53 +0800 Subject: [PATCH 01/11] init Signed-off-by: crazycs --- planner/core/common_plans.go | 78 +++++++++++++++++++----------------- planner/core/encode.go | 3 +- util/plancodec/codec.go | 11 ++++- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/planner/core/common_plans.go b/planner/core/common_plans.go index 411e56235e1d6..41e63488774bd 100644 --- a/planner/core/common_plans.go +++ b/planner/core/common_plans.go @@ -937,6 +937,47 @@ func (e *Explain) explainPlanInRowFormat(p Plan, taskType, driverSide, indent st return } +func getRuntimeInfo(ctx sessionctx.Context, p Plan) (actRows, analyzeInfo, memoryInfo, diskInfo string) { + runtimeStatsColl := ctx.GetSessionVars().StmtCtx.RuntimeStatsColl + if runtimeStatsColl == nil { + return + } + explainID := p.ExplainID().String() + + // There maybe some mock information for cop task to let runtimeStatsColl.Exists(p.ExplainID()) is true. + // So check copTaskEkxecDetail first and print the real cop task information if it's not empty. + if runtimeStatsColl.ExistsCopStats(explainID) { + copstats := runtimeStatsColl.GetCopStats(explainID) + analyzeInfo = copstats.String() + actRows = fmt.Sprint(copstats.GetActRows()) + } else if runtimeStatsColl.ExistsRootStats(explainID) { + rootstats := runtimeStatsColl.GetRootStats(explainID) + analyzeInfo = rootstats.String() + actRows = fmt.Sprint(rootstats.GetActRows()) + } else { + analyzeInfo = "time:0ns, loops:0" + } + switch p.(type) { + case *PhysicalTableReader, *PhysicalIndexReader, *PhysicalIndexLookUpReader: + if s := runtimeStatsColl.GetReaderStats(explainID); s != nil && len(s.String()) > 0 { + analyzeInfo += ", " + s.String() + } + } + + memoryInfo = "N/A" + memTracker := ctx.GetSessionVars().StmtCtx.MemTracker.SearchTracker(p.ExplainID().String()) + if memTracker != nil { + memoryInfo = memTracker.BytesToString(memTracker.MaxConsumed()) + } + + diskInfo = "N/A" + diskTracker := ctx.GetSessionVars().StmtCtx.DiskTracker.SearchTracker(p.ExplainID().String()) + if diskTracker != nil { + diskInfo = diskTracker.BytesToString(diskTracker.MaxConsumed()) + } + return +} + // prepareOperatorInfo generates the following information for every plan: // operator id, estimated rows, task type, access object and other operator info. func (e *Explain) prepareOperatorInfo(p Plan, taskType, driverSide, indent string, isLastChild bool) { @@ -961,42 +1002,7 @@ func (e *Explain) prepareOperatorInfo(p Plan, taskType, driverSide, indent strin var row []string if e.Analyze { - runtimeStatsColl := e.ctx.GetSessionVars().StmtCtx.RuntimeStatsColl - explainID := p.ExplainID().String() - var actRows, analyzeInfo string - - // There maybe some mock information for cop task to let runtimeStatsColl.Exists(p.ExplainID()) is true. - // So check copTaskEkxecDetail first and print the real cop task information if it's not empty. - if runtimeStatsColl.ExistsCopStats(explainID) { - copstats := runtimeStatsColl.GetCopStats(explainID) - analyzeInfo = copstats.String() - actRows = fmt.Sprint(copstats.GetActRows()) - } else if runtimeStatsColl.ExistsRootStats(explainID) { - rootstats := runtimeStatsColl.GetRootStats(explainID) - analyzeInfo = rootstats.String() - actRows = fmt.Sprint(rootstats.GetActRows()) - } else { - analyzeInfo = "time:0ns, loops:0" - } - switch p.(type) { - case *PhysicalTableReader, *PhysicalIndexReader, *PhysicalIndexLookUpReader: - if s := runtimeStatsColl.GetReaderStats(explainID); s != nil && len(s.String()) > 0 { - analyzeInfo += ", " + s.String() - } - } - - memoryInfo := "N/A" - memTracker := e.ctx.GetSessionVars().StmtCtx.MemTracker.SearchTracker(p.ExplainID().String()) - if memTracker != nil { - memoryInfo = memTracker.BytesToString(memTracker.MaxConsumed()) - } - - diskInfo := "N/A" - diskTracker := e.ctx.GetSessionVars().StmtCtx.DiskTracker.SearchTracker(p.ExplainID().String()) - if diskTracker != nil { - diskInfo = diskTracker.BytesToString(diskTracker.MaxConsumed()) - } - + actRows, analyzeInfo, memoryInfo, diskInfo := getRuntimeInfo(e.ctx, p) row = []string{id, estRows, actRows, taskType, accessObject, analyzeInfo, operatorInfo, memoryInfo, diskInfo} } else { row = []string{id, estRows, taskType, accessObject, operatorInfo} diff --git a/planner/core/encode.go b/planner/core/encode.go index ca8ee7bcabb5f..e9f70f54b42e5 100644 --- a/planner/core/encode.go +++ b/planner/core/encode.go @@ -57,7 +57,8 @@ func (pn *planEncoder) encodePlanTree(p PhysicalPlan) string { } func (pn *planEncoder) encodePlan(p PhysicalPlan, isRoot bool, depth int) { - plancodec.EncodePlanNode(depth, p.ID(), p.TP(), isRoot, p.statsInfo().RowCount, p.ExplainInfo(), &pn.buf) + actRows, analyzeInfo, memoryInfo, diskInfo := getRuntimeInfo(p.SCtx(), p) + plancodec.EncodePlanNode(depth, p.ID(), p.TP(), isRoot, p.statsInfo().RowCount, p.ExplainInfo(), actRows, analyzeInfo, memoryInfo, diskInfo, &pn.buf) pn.encodedPlans[p.ID()] = true depth++ diff --git a/util/plancodec/codec.go b/util/plancodec/codec.go index 971bb94d84f75..f532f3ac7c876 100644 --- a/util/plancodec/codec.go +++ b/util/plancodec/codec.go @@ -253,7 +253,8 @@ func decodePlanInfo(str string) (*planInfo, error) { } // EncodePlanNode is used to encode the plan to a string. -func EncodePlanNode(depth, pid int, planType string, isRoot bool, rowCount float64, explainInfo string, buf *bytes.Buffer) { +func EncodePlanNode(depth, pid int, planType string, isRoot bool, rowCount float64, + explainInfo, actRows, analyzeInfo, memoryInfo, diskInfo string, buf *bytes.Buffer) { buf.WriteString(strconv.Itoa(depth)) buf.WriteByte(separator) buf.WriteString(encodeID(planType, pid)) @@ -267,6 +268,14 @@ func EncodePlanNode(depth, pid int, planType string, isRoot bool, rowCount float buf.WriteString(strconv.FormatFloat(rowCount, 'f', -1, 64)) buf.WriteByte(separator) buf.WriteString(explainInfo) + buf.WriteByte(separator) + buf.WriteString(actRows) + buf.WriteByte(separator) + buf.WriteString(analyzeInfo) + buf.WriteByte(separator) + buf.WriteString(memoryInfo) + buf.WriteByte(separator) + buf.WriteString(diskInfo) buf.WriteByte(lineBreaker) } From cb3b2dd76c736435b04cd82d953e1e711d880399 Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 2 Jun 2020 09:10:19 +0800 Subject: [PATCH 02/11] fix test Signed-off-by: crazycs --- util/plancodec/codec.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/util/plancodec/codec.go b/util/plancodec/codec.go index f532f3ac7c876..3ac42de610b4c 100644 --- a/util/plancodec/codec.go +++ b/util/plancodec/codec.go @@ -268,14 +268,17 @@ func EncodePlanNode(depth, pid int, planType string, isRoot bool, rowCount float buf.WriteString(strconv.FormatFloat(rowCount, 'f', -1, 64)) buf.WriteByte(separator) buf.WriteString(explainInfo) - buf.WriteByte(separator) - buf.WriteString(actRows) - buf.WriteByte(separator) - buf.WriteString(analyzeInfo) - buf.WriteByte(separator) - buf.WriteString(memoryInfo) - buf.WriteByte(separator) - buf.WriteString(diskInfo) + // Check whether has runtime info. + if len(actRows) > 0 && len(analyzeInfo) > 0 && len(memoryInfo) > 0 && len(diskInfo) > 0 { + buf.WriteByte(separator) + buf.WriteString(actRows) + buf.WriteByte(separator) + buf.WriteString(analyzeInfo) + buf.WriteByte(separator) + buf.WriteString(memoryInfo) + buf.WriteByte(separator) + buf.WriteString(diskInfo) + } buf.WriteByte(lineBreaker) } From 1a7b7bcfb79198dd19c48af4c7a8dfeadcbc99ab Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 2 Jun 2020 09:58:18 +0800 Subject: [PATCH 03/11] add test1 Signed-off-by: crazycs --- expression/integration_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/expression/integration_test.go b/expression/integration_test.go index a09150cdb60ff..5d2b35bdb2c44 100755 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -4400,6 +4400,16 @@ func (s *testIntegrationSuite) TestTiDBDecodePlanFunc(c *C) { "\t └─TableReader_21 \troot\t0 \tdata:Selection_20\n" + "\t └─Selection_20 \tcop \t0 \tlt(Column#9, NULL), not(isnull(Column#10)), not(isnull(Column#9))\n" + "\t └─TableScan_19\tcop \t10000\ttable:t2, range:[-inf,+inf], keep order:false, stats:pseudo")) + tk.MustQuery("select tidb_decode_plan('rwPwcTAJNV8xNAkwCTEJZnVuY3M6bWF4KHRlc3QudC5hKS0+Q29sdW1uIzQJMQl0aW1lOj" + + "IyMy45MzXCtXMsIGxvb3BzOjIJMTI4IEJ5dGVzCU4vQQoxCTE2XzE4CTAJMQlvZmZzZXQ6MCwgY291bnQ6MQkxCQlHFDE4LjQyMjJHAAhOL0" + + "EBBCAKMgkzMl8yOAkBlEBpbmRleDpMaW1pdF8yNwkxCQ0+DDYuODUdPSwxLCBycGMgbnVtOiANDAUpGDE1MC44MjQFKjhwcm9jIGtleXM6MA" + + "kxOTgdsgAzAbIAMgFearIAFDU3LjM5NgVKAGwN+BGxIDQJMTNfMjYJMQGgHGFibGU6dCwgCbqwaWR4KGEpLCByYW5nZTooMCwraW5mXSwga2" + + "VlcCBvcmRlcjp0cnVlLCBkZXNjAT8kaW1lOjU2LjY2MR1rJDEJTi9BCU4vQQo=')").Check(testkit.Rows("" + + "\tStreamAgg_14 \troot\t1\tfuncs:max(test.t.a)->Column#4 \t1\ttime:223.935µs, loops:2 \t128 Bytes\tN/A\n" + + "\t└─Limit_18 \troot\t1\toffset:0, count:1 \t1\ttime:218.422µs, loops:2 \tN/A \tN/A\n" + + "\t └─IndexReader_28 \troot\t1\tindex:Limit_27 \t1\ttime:216.85µs, loops:1, rpc num: 1, rpc time:150.824µs, proc keys:0\t198 Bytes\tN/A\n" + + "\t └─Limit_27 \tcop \t1\toffset:0, count:1 \t1\ttime:57.396µs, loops:2 \tN/A \tN/A\n" + + "\t └─IndexScan_26\tcop \t1\ttable:t, index:idx(a), range:(0,+inf], keep order:true, desc\t1\ttime:56.661µs, loops:1 \tN/A \tN/A")) } func (s *testIntegrationSuite) TestTiDBInternalFunc(c *C) { From 5ca3ee87a44f37352d4dcd0f84714456265baac8 Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 2 Jun 2020 10:17:45 +0800 Subject: [PATCH 04/11] add test2 Signed-off-by: crazycs --- planner/core/plan_test.go | 25 +++++++++++++++++++++++++ util/plancodec/codec.go | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/planner/core/plan_test.go b/planner/core/plan_test.go index a166a5cd7b668..c4af9acde2529 100644 --- a/planner/core/plan_test.go +++ b/planner/core/plan_test.go @@ -84,6 +84,31 @@ func (s *testPlanNormalize) TestNormalizedPlan(c *C) { } } +func (s *testPlanNormalize) TestEncodeDecodePlan(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t1,t2") + tk.MustExec("create table t1 (a int key,b int,c int, index (b));") + tk.MustExec("set tidb_enable_collect_execution_info=1;") + + tk.Se.GetSessionVars().PlanID = 0 + tk.MustExec("select max(a) from t1 where a>0;") + info := tk.Se.ShowProcess() + c.Assert(info, NotNil) + p, ok := info.Plan.(core.Plan) + c.Assert(ok, IsTrue) + encodeStr := core.EncodePlan(p) + planTree, err := plancodec.DecodePlan(encodeStr) + c.Assert(err, IsNil) + result := "" + + "\tStreamAgg_13 \troot\t1\tfuncs:max(test.t1.a)->Column#4 \t0\ttime:0s, loops:0 \t121 Bytes\tN/A\n" + + "\t└─Limit_17 \troot\t1\toffset:0, count:1 \t0\ttime:0s, loops:0 \tN/A \tN/A\n" + + "\t └─TableReader_27 \troot\t1\tdata:Limit_26 \t0\ttime:0s, loops:0 \t0 Bytes \tN/A\n" + + "\t └─Limit_26 \tcop \t1\toffset:0, count:1 \t \ttime:0ns, loops:0\tN/A \tN/A\n" + + "\t └─TableScan_25\tcop \t1\ttable:t1, range:(0,+inf], keep order:true, desc, stats:pseudo\t0\ttime:0s, loops:0 \tN/A \tN/A" + c.Assert(planTree, Equals, result) +} + func (s *testPlanNormalize) TestNormalizedDigest(c *C) { tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") diff --git a/util/plancodec/codec.go b/util/plancodec/codec.go index 3ac42de610b4c..7ca59347d4081 100644 --- a/util/plancodec/codec.go +++ b/util/plancodec/codec.go @@ -269,7 +269,7 @@ func EncodePlanNode(depth, pid int, planType string, isRoot bool, rowCount float buf.WriteByte(separator) buf.WriteString(explainInfo) // Check whether has runtime info. - if len(actRows) > 0 && len(analyzeInfo) > 0 && len(memoryInfo) > 0 && len(diskInfo) > 0 { + if len(actRows) > 0 || len(analyzeInfo) > 0 || len(memoryInfo) > 0 || len(diskInfo) > 0 { buf.WriteByte(separator) buf.WriteString(actRows) buf.WriteByte(separator) From 90121f88dbb1bc06b5cd2fe6129c7f3a0cbde424 Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 2 Jun 2020 10:30:50 +0800 Subject: [PATCH 05/11] fix panic Signed-off-by: crazycs --- util/plancodec/codec.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/util/plancodec/codec.go b/util/plancodec/codec.go index 7ca59347d4081..8ff46b38cb66c 100644 --- a/util/plancodec/codec.go +++ b/util/plancodec/codec.go @@ -177,6 +177,19 @@ func (pd *planDecoder) alignFields(planInfos []*planInfo) { if len(planInfos) == 0 { return } + // Align fields length. Some plan may doesn't have runtime info, need append `` to align with other plan fields. + maxLen := -1 + for _, p := range planInfos { + if len(p.fields) > maxLen { + maxLen = len(p.fields) + } + } + for _, p := range planInfos { + for len(p.fields) < maxLen { + p.fields = append(p.fields, "") + } + } + fieldsLen := len(planInfos[0].fields) // Last field no need to align. fieldsLen-- From 73b1026f86f7a69c0490c85ae0a18c530375a287 Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 2 Jun 2020 10:49:23 +0800 Subject: [PATCH 06/11] make test stable Signed-off-by: crazycs --- planner/core/plan_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/planner/core/plan_test.go b/planner/core/plan_test.go index c4af9acde2529..a1c63aaa9ebec 100644 --- a/planner/core/plan_test.go +++ b/planner/core/plan_test.go @@ -101,12 +101,14 @@ func (s *testPlanNormalize) TestEncodeDecodePlan(c *C) { planTree, err := plancodec.DecodePlan(encodeStr) c.Assert(err, IsNil) result := "" + - "\tStreamAgg_13 \troot\t1\tfuncs:max(test.t1.a)->Column#4 \t0\ttime:0s, loops:0 \t121 Bytes\tN/A\n" + + "\tStreamAgg_13 \troot\t1\tfuncs:max.*->Column#4 \t0\ttime:0s, loops:0 \t.* Bytes\tN/A\n" + "\t└─Limit_17 \troot\t1\toffset:0, count:1 \t0\ttime:0s, loops:0 \tN/A \tN/A\n" + - "\t └─TableReader_27 \troot\t1\tdata:Limit_26 \t0\ttime:0s, loops:0 \t0 Bytes \tN/A\n" + + "\t └─TableReader_27 \troot\t1\tdata:Limit_26 \t0\ttime:0s, loops:0 \t.* Bytes \tN/A\n" + "\t └─Limit_26 \tcop \t1\toffset:0, count:1 \t \ttime:0ns, loops:0\tN/A \tN/A\n" + - "\t └─TableScan_25\tcop \t1\ttable:t1, range:(0,+inf], keep order:true, desc, stats:pseudo\t0\ttime:0s, loops:0 \tN/A \tN/A" - c.Assert(planTree, Equals, result) + "\t └─TableScan_25\tcop \t1\ttable:t1, range:.*, keep order:true, desc, stats:pseudo\t0\ttime:0s, loops:0 \tN/A \tN/A" + planTree = strings.Replace(planTree, "\n", "", -1) + result = strings.Replace(result, "\n", "", -1) + c.Assert(planTree, Matches, result) } func (s *testPlanNormalize) TestNormalizedDigest(c *C) { From cd1601ba80c03e1070a0edfa5c2ba595c5a7d6bd Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 2 Jun 2020 22:58:36 +0800 Subject: [PATCH 07/11] try to fix test Signed-off-by: crazycs --- planner/core/plan_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planner/core/plan_test.go b/planner/core/plan_test.go index a1c63aaa9ebec..d0ac7d5cee735 100644 --- a/planner/core/plan_test.go +++ b/planner/core/plan_test.go @@ -103,7 +103,7 @@ func (s *testPlanNormalize) TestEncodeDecodePlan(c *C) { result := "" + "\tStreamAgg_13 \troot\t1\tfuncs:max.*->Column#4 \t0\ttime:0s, loops:0 \t.* Bytes\tN/A\n" + "\t└─Limit_17 \troot\t1\toffset:0, count:1 \t0\ttime:0s, loops:0 \tN/A \tN/A\n" + - "\t └─TableReader_27 \troot\t1\tdata:Limit_26 \t0\ttime:0s, loops:0 \t.* Bytes \tN/A\n" + + "\t └─TableReader_27 \troot\t1\tdata:Limit_26 \t0\ttime:0s, loops:0 \t.*\tN/A\n" + "\t └─Limit_26 \tcop \t1\toffset:0, count:1 \t \ttime:0ns, loops:0\tN/A \tN/A\n" + "\t └─TableScan_25\tcop \t1\ttable:t1, range:.*, keep order:true, desc, stats:pseudo\t0\ttime:0s, loops:0 \tN/A \tN/A" planTree = strings.Replace(planTree, "\n", "", -1) From d7dd02f0447c69c6457b1200fa0d3408caa55d7f Mon Sep 17 00:00:00 2001 From: crazycs Date: Tue, 2 Jun 2020 23:09:27 +0800 Subject: [PATCH 08/11] try to fix test race Signed-off-by: crazycs --- planner/core/plan_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/planner/core/plan_test.go b/planner/core/plan_test.go index d0ac7d5cee735..4936518320392 100644 --- a/planner/core/plan_test.go +++ b/planner/core/plan_test.go @@ -20,6 +20,7 @@ import ( "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/util/israce" "github.com/pingcap/tidb/util/plancodec" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" @@ -85,6 +86,9 @@ func (s *testPlanNormalize) TestNormalizedPlan(c *C) { } func (s *testPlanNormalize) TestEncodeDecodePlan(c *C) { + if israce.RaceEnabled { + c.Skip("skip race test") + } tk := testkit.NewTestKit(c, s.store) tk.MustExec("use test") tk.MustExec("drop table if exists t1,t2") From 2940cda89ac4076e3273d47f2dc6bdccd05da598 Mon Sep 17 00:00:00 2001 From: crazycs Date: Wed, 3 Jun 2020 11:23:52 +0800 Subject: [PATCH 09/11] open collect runtime information to bench Signed-off-by: crazycs --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 6c0f1c0787d7b..2f4a1a0fb2cf9 100644 --- a/config/config.go +++ b/config/config.go @@ -683,7 +683,7 @@ var defaultConf = Config{ AllowAutoRandom: false, AllowsExpressionIndex: false, }, - EnableCollectExecutionInfo: false, + EnableCollectExecutionInfo: true, } var ( From be4b51b85672093f3647ac1a72d64af2aca793fe Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Mon, 8 Jun 2020 17:46:53 +0800 Subject: [PATCH 10/11] close collect runtime information by default Signed-off-by: crazycs520 --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index b06b54b1ced06..5b8f21c0267c9 100644 --- a/config/config.go +++ b/config/config.go @@ -683,7 +683,7 @@ var defaultConf = Config{ AllowAutoRandom: false, AllowsExpressionIndex: false, }, - EnableCollectExecutionInfo: true, + EnableCollectExecutionInfo: false, } var ( From d4a86d69564972b82addd148499db712539f046b Mon Sep 17 00:00:00 2001 From: crazycs520 Date: Tue, 9 Jun 2020 11:05:42 +0800 Subject: [PATCH 11/11] fix test Signed-off-by: crazycs520 --- planner/core/plan_test.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/planner/core/plan_test.go b/planner/core/plan_test.go index 4936518320392..fa9936b0c55f3 100644 --- a/planner/core/plan_test.go +++ b/planner/core/plan_test.go @@ -104,15 +104,8 @@ func (s *testPlanNormalize) TestEncodeDecodePlan(c *C) { encodeStr := core.EncodePlan(p) planTree, err := plancodec.DecodePlan(encodeStr) c.Assert(err, IsNil) - result := "" + - "\tStreamAgg_13 \troot\t1\tfuncs:max.*->Column#4 \t0\ttime:0s, loops:0 \t.* Bytes\tN/A\n" + - "\t└─Limit_17 \troot\t1\toffset:0, count:1 \t0\ttime:0s, loops:0 \tN/A \tN/A\n" + - "\t └─TableReader_27 \troot\t1\tdata:Limit_26 \t0\ttime:0s, loops:0 \t.*\tN/A\n" + - "\t └─Limit_26 \tcop \t1\toffset:0, count:1 \t \ttime:0ns, loops:0\tN/A \tN/A\n" + - "\t └─TableScan_25\tcop \t1\ttable:t1, range:.*, keep order:true, desc, stats:pseudo\t0\ttime:0s, loops:0 \tN/A \tN/A" - planTree = strings.Replace(planTree, "\n", "", -1) - result = strings.Replace(result, "\n", "", -1) - c.Assert(planTree, Matches, result) + c.Assert(strings.Contains(planTree, "time"), IsTrue) + c.Assert(strings.Contains(planTree, "loops"), IsTrue) } func (s *testPlanNormalize) TestNormalizedDigest(c *C) {