Skip to content
39 changes: 39 additions & 0 deletions pkg/common/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,42 @@ func ConvertBytesToHumanReadable(bytes int64) string {
}
return fmt.Sprintf("%.2f TiB", num/TiB)
}

// FormatBytes formats bytes to human-readable string using decimal units (B, KB, MB, GB, TB)
// Examples: 0 -> "0B", 1386624 -> "1.39MB", 1024 -> "1.02KB"
// This uses decimal units (1000-based) instead of 1024-based)
func FormatBytes(bytes int64) string {
if bytes == 0 {
return "0B"
}
num := float64(bytes)
if bytes < THOUSAND {
return fmt.Sprintf("%dB", bytes)
}
if bytes < MILLION {
return fmt.Sprintf("%.2fKB", num/THOUSAND)
}
if bytes < BILLION {
return fmt.Sprintf("%.2fMB", num/MILLION)
}
if bytes < TRILLION {
return fmt.Sprintf("%.2fGB", num/BILLION)
}
return fmt.Sprintf("%.2fTB", num/TRILLION)
}

// FormatDuration formats nanoseconds to human-readable string (ms or s)
// Examples: 0 -> "0ms", 21625539 -> "21.63ms", 1000000000 -> "1.00s"
func FormatDuration(ns int64) string {
if ns == 0 {
return "0ns"
}
const (
nanosPerMilli = 1000000
nanosPerSec = 1000000000
)
if ns < nanosPerSec {
return fmt.Sprintf("%.2fms", float64(ns)/nanosPerMilli)
}
return fmt.Sprintf("%.2fs", float64(ns)/nanosPerSec)
}
40 changes: 32 additions & 8 deletions pkg/frontend/mysql_cmd_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3621,21 +3621,23 @@ func buildErrorJsonPlan(buffer *bytes.Buffer, uuid uuid.UUID, errcode uint16, ms
}

type jsonPlanHandler struct {
jsonBytes []byte
statsBytes statistic.StatsArray
stats motrace.Statistic
buffer *bytes.Buffer
jsonBytes []byte
statsBytes statistic.StatsArray
stats motrace.Statistic
buffer *bytes.Buffer
marshalPlan *models.ExplainData // Store ExplainData reference to generate text output
}

func NewJsonPlanHandler(ctx context.Context, stmt *motrace.StatementInfo, ses FeSession, plan *plan2.Plan, phyPlan *models.PhyPlan, opts ...marshalPlanOptions) *jsonPlanHandler {
h := NewMarshalPlanHandler(ctx, stmt, plan, phyPlan, opts...)
jsonBytes := h.Marshal(ctx)
statsBytes, stats := h.Stats(ctx, ses)
return &jsonPlanHandler{
jsonBytes: jsonBytes,
statsBytes: statsBytes,
stats: stats,
buffer: h.handoverBuffer(),
jsonBytes: jsonBytes,
statsBytes: statsBytes,
stats: stats,
buffer: h.handoverBuffer(),
marshalPlan: h.marshalPlan, // Store ExplainData reference
}
}

Expand All @@ -3644,6 +3646,28 @@ func (h *jsonPlanHandler) Stats(ctx context.Context) (statistic.StatsArray, motr
}

func (h *jsonPlanHandler) Marshal(ctx context.Context) []byte {
// If marshalPlan is available and has physical plan, generate analyze mode text output
// Otherwise, return original JSON (for error cases or when plan is not available)
if h.marshalPlan != nil && (len(h.marshalPlan.PhyPlan.LocalScope) > 0 || len(h.marshalPlan.PhyPlan.RemoteScope) > 0) {
// Check execution time to determine output level
// Get total execution time from ExecutionDuration
totalExecTime := h.marshalPlan.NewPlanStats.ExecuteStage.ExecutionDuration
longQueryThreshold := motrace.GetLongQueryTime()

// If execution time > (5s + longQueryThreshold) or > 3x longQueryThreshold, include Physical Plan
includePhysicalPlan := totalExecTime > (5*time.Second+longQueryThreshold) || totalExecTime > 3*longQueryThreshold

var phyplanText string
if includePhysicalPlan {
// Generate full analyze mode text output including Physical Plan
phyplanText = models.ExplainPhyPlan(&h.marshalPlan.PhyPlan, &h.marshalPlan.NewPlanStats, models.AnalyzeOption)
} else {
// Generate only Overview section (without Physical Plan)
phyplanText = models.ExplainPhyPlanOverview(&h.marshalPlan.PhyPlan, &h.marshalPlan.NewPlanStats, models.AnalyzeOption)
}
return []byte(phyplanText)
}
// Fall back to original JSON for error cases or when physical plan is not available
return h.jsonBytes
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/compile/analyze_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ func explainPipeline(node vm.Operator, prefix string, isRoot bool, isTail bool,

analyzeStr := ""
if option.Verbose {
analyzeStr = fmt.Sprintf("(idx:%v, isFirst:%v, isLast:%v)",
analyzeStr = fmt.Sprintf("(%v,%v,%v)",
node.GetOperatorBase().Idx,
node.GetOperatorBase().IsFirst,
node.GetOperatorBase().IsLast)
Expand Down
40 changes: 20 additions & 20 deletions pkg/sql/compile/analyze_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,31 @@ func Test_processPhyScope(t *testing.T) {
Version: 1.0, S3IOInputCount: 0, S3IOOutputCount: 0
LOCAL SCOPES:
Scope 1 (Magic: Merge, mcpu: 1, Receiver: [4])
Pipeline: └── Output (idx:-1, isFirst:false, isLast:false)
└── Projection (idx:2, isFirst:true, isLast:true)
└── Projection (idx:1, isFirst:false, isLast:true)
└── MergeGroup (idx:1, isFirst:false, isLast:false)
└── Merge (idx:1, isFirst:false, isLast:false)
Pipeline: └── Output (-1,false,false)
└── Projection (2,true,true)
└── Projection (1,false,true)
└── MergeGroup (1,false,false)
└── Merge (1,false,false)
PreScopes: {
Scope 2 (Magic: Normal, mcpu: 4, Receiver: [0, 1, 2, 3])
Pipeline: └── Connector (idx:1, isFirst:false, isLast:false) to MergeReceiver 4
└── MergeGroup (idx:1, isFirst:false, isLast:false)
└── Merge (idx:1, isFirst:false, isLast:false)
Pipeline: └── Connector (1,false,false) to MergeReceiver 4
└── MergeGroup (1,false,false)
└── Merge (1,false,false)
PreScopes: {
Scope 3 (Magic: Normal, mcpu: 1, Receiver: [])
Scope 3 (Magic: Normal, mcpu: 1)
DataSource: cloud_device.real_time_position[time_stamp distance]
Pipeline: └── Connector (idx:0, isFirst:false, isLast:false) to MergeReceiver 0
└── Group (idx:1, isFirst:true, isLast:false)
└── Projection (idx:0, isFirst:false, isLast:true)
└── Filter (idx:0, isFirst:false, isLast:false)
└── TableScan (idx:0, isFirst:true, isLast:false)
Scope 4 (Magic: Normal, mcpu: 1, Receiver: [])
Pipeline: └── Connector (0,false,false) to MergeReceiver 0
└── Group (1,true,false)
└── Projection (0,false,true)
└── Filter (0,false,false)
└── TableScan (0,true,false)
Scope 4 (Magic: Normal, mcpu: 1)
DataSource: cloud_device.real_time_position[time_stamp distance]
Pipeline: └── Connector (idx:0, isFirst:false, isLast:false) to MergeReceiver 1
└── Group (idx:1, isFirst:true, isLast:false)
└── Projection (idx:0, isFirst:false, isLast:true)
└── Filter (idx:0, isFirst:false, isLast:false)
└── TableScan (idx:0, isFirst:true, isLast:false)
Pipeline: └── Connector (0,false,false) to MergeReceiver 1
└── Group (1,true,false)
└── Projection (0,false,true)
└── Filter (0,false,false)
└── TableScan (0,true,false)
}
}
*/
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/compile/debugTools.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func ShowPipelineTree(

analyzeStr := ""
if level == VerboseLevel || level == AnalyzeLevel {
analyzeStr = fmt.Sprintf("(idx:%v, isFirst:%v, isLast:%v)",
analyzeStr = fmt.Sprintf("(%v,%v,%v)",
node.GetOperatorBase().Idx,
node.GetOperatorBase().IsFirst,
node.GetOperatorBase().IsLast)
Expand Down
Loading
Loading