[SPARK-15792][SQL] Allows operator to change the verbosity in explain output

## What changes were proposed in this pull request?

This PR allows customization of verbosity in explain output. After change, `dataframe.explain()` and `dataframe.explain(true)` has different verbosity output for physical plan.

Currently, this PR only enables verbosity string for operator `HashAggregateExec` and `SortAggregateExec`. We will gradually enable verbosity string for more operators in future.

**Less verbose mode:** dataframe.explain(extended = false)

`output=[count(a)#85L]` is **NOT** displayed for HashAggregate.

```
scala> Seq((1,2,3)).toDF("a", "b", "c").createTempView("df2")
scala> spark.sql("select count(a) from df2").explain()
== Physical Plan ==
*HashAggregate(key=[], functions=[count(1)])
+- Exchange SinglePartition
   +- *HashAggregate(key=[], functions=[partial_count(1)])
      +- LocalTableScan
```

**Verbose mode:** dataframe.explain(extended = true)

`output=[count(a)#85L]` is displayed for HashAggregate.

```
scala> spark.sql("select count(a) from df2").explain(true)  // "output=[count(a)#85L]" is added
...
== Physical Plan ==
*HashAggregate(key=[], functions=[count(1)], output=[count(a)#85L])
+- Exchange SinglePartition
   +- *HashAggregate(key=[], functions=[partial_count(1)], output=[count#87L])
      +- LocalTableScan
```

## How was this patch tested?

Manual test.

Author: Sean Zhong <seanzhong@databricks.com>

Closes #13535 from clockfly/verbose_breakdown_2.
This commit is contained in:
Sean Zhong 2016-06-06 22:59:25 -07:00 committed by Cheng Lian
parent 0e0904a2fc
commit 5f731d6859
7 changed files with 55 additions and 18 deletions

View file

@ -190,6 +190,10 @@ abstract class Expression extends TreeNode[Expression] {
case single => single :: Nil
}
// Marks this as final, Expression.verboseString should never be called, and thus shouldn't be
// overridden by concrete classes.
final override def verboseString: String = simpleString
override def simpleString: String = toString
override def toString: String = prettyName + flatArguments.mkString("(", ", ", ")")

View file

@ -257,6 +257,8 @@ abstract class QueryPlan[PlanType <: QueryPlan[PlanType]] extends TreeNode[PlanT
override def simpleString: String = statePrefix + super.simpleString
override def verboseString: String = simpleString
/**
* All the subqueries of current plan.
*/

View file

@ -462,10 +462,17 @@ abstract class TreeNode[BaseType <: TreeNode[BaseType]] extends Product {
/** ONE line description of this node. */
def simpleString: String = s"$nodeName $argString".trim
/** ONE line description of this node with more information */
def verboseString: String
override def toString: String = treeString
/** Returns a string representation of the nodes in this tree */
def treeString: String = generateTreeString(0, Nil, new StringBuilder).toString
def treeString: String = treeString(verbose = true)
def treeString(verbose: Boolean): String = {
generateTreeString(0, Nil, new StringBuilder, verbose).toString
}
/**
* Returns a string representation of the nodes in this tree, where each operator is numbered.
@ -508,6 +515,7 @@ abstract class TreeNode[BaseType <: TreeNode[BaseType]] extends Product {
depth: Int,
lastChildren: Seq[Boolean],
builder: StringBuilder,
verbose: Boolean,
prefix: String = ""): StringBuilder = {
if (depth > 0) {
lastChildren.init.foreach { isLast =>
@ -520,18 +528,21 @@ abstract class TreeNode[BaseType <: TreeNode[BaseType]] extends Product {
}
builder.append(prefix)
builder.append(simpleString)
val headline = if (verbose) verboseString else simpleString
builder.append(headline)
builder.append("\n")
if (innerChildren.nonEmpty) {
innerChildren.init.foreach(_.generateTreeString(
depth + 2, lastChildren :+ false :+ false, builder))
innerChildren.last.generateTreeString(depth + 2, lastChildren :+ false :+ true, builder)
depth + 2, lastChildren :+ false :+ false, builder, verbose))
innerChildren.last.generateTreeString(
depth + 2, lastChildren :+ false :+ true, builder, verbose)
}
if (children.nonEmpty) {
children.init.foreach(_.generateTreeString(depth + 1, lastChildren :+ false, builder, prefix))
children.last.generateTreeString(depth + 1, lastChildren :+ true, builder, prefix)
children.init.foreach(
_.generateTreeString(depth + 1, lastChildren :+ false, builder, verbose, prefix))
children.last.generateTreeString(depth + 1, lastChildren :+ true, builder, verbose, prefix)
}
builder

View file

@ -201,24 +201,26 @@ class QueryExecution(val sparkSession: SparkSession, val logical: LogicalPlan) {
def simpleString: String = {
s"""== Physical Plan ==
|${stringOrError(executedPlan)}
|${stringOrError(executedPlan.treeString(verbose = false))}
""".stripMargin.trim
}
override def toString: String = {
def output =
analyzed.output.map(o => s"${o.name}: ${o.dataType.simpleString}").mkString(", ")
val analyzedPlan =
Seq(stringOrError(output), stringOrError(analyzed)).filter(_.nonEmpty).mkString("\n")
val analyzedPlan = Seq(
stringOrError(output),
stringOrError(analyzed.treeString(verbose = true))
).filter(_.nonEmpty).mkString("\n")
s"""== Parsed Logical Plan ==
|${stringOrError(logical)}
|${stringOrError(logical.treeString(verbose = true))}
|== Analyzed Logical Plan ==
|$analyzedPlan
|== Optimized Logical Plan ==
|${stringOrError(optimizedPlan)}
|${stringOrError(optimizedPlan.treeString(verbose = true))}
|== Physical Plan ==
|${stringOrError(executedPlan)}
|${stringOrError(executedPlan.treeString(verbose = true))}
""".stripMargin.trim
}

View file

@ -250,8 +250,9 @@ case class InputAdapter(child: SparkPlan) extends UnaryExecNode with CodegenSupp
depth: Int,
lastChildren: Seq[Boolean],
builder: StringBuilder,
verbose: Boolean,
prefix: String = ""): StringBuilder = {
child.generateTreeString(depth, lastChildren, builder, "")
child.generateTreeString(depth, lastChildren, builder, verbose, "")
}
}
@ -407,8 +408,9 @@ case class WholeStageCodegenExec(child: SparkPlan) extends UnaryExecNode with Co
depth: Int,
lastChildren: Seq[Boolean],
builder: StringBuilder,
verbose: Boolean,
prefix: String = ""): StringBuilder = {
child.generateTreeString(depth, lastChildren, builder, "*")
child.generateTreeString(depth, lastChildren, builder, verbose, "*")
}
}

View file

@ -764,7 +764,11 @@ case class HashAggregateExec(
"""
}
override def simpleString: String = {
override def verboseString: String = toString(verbose = true)
override def simpleString: String = toString(verbose = false)
private def toString(verbose: Boolean): String = {
val allAggregateExpressions = aggregateExpressions
testFallbackStartsAt match {
@ -772,7 +776,11 @@ case class HashAggregateExec(
val keyString = groupingExpressions.mkString("[", ",", "]")
val functionString = allAggregateExpressions.mkString("[", ",", "]")
val outputString = output.mkString("[", ",", "]")
if (verbose) {
s"HashAggregate(key=$keyString, functions=$functionString, output=$outputString)"
} else {
s"HashAggregate(key=$keyString, functions=$functionString)"
}
case Some(fallbackStartsAt) =>
s"HashAggregateWithControlledFallback $groupingExpressions " +
s"$allAggregateExpressions $resultExpressions fallbackStartsAt=$fallbackStartsAt"

View file

@ -103,12 +103,20 @@ case class SortAggregateExec(
}
}
override def simpleString: String = {
override def simpleString: String = toString(verbose = false)
override def verboseString: String = toString(verbose = true)
private def toString(verbose: Boolean): String = {
val allAggregateExpressions = aggregateExpressions
val keyString = groupingExpressions.mkString("[", ",", "]")
val functionString = allAggregateExpressions.mkString("[", ",", "]")
val outputString = output.mkString("[", ",", "]")
if (verbose) {
s"SortAggregate(key=$keyString, functions=$functionString, output=$outputString)"
} else {
s"SortAggregate(key=$keyString, functions=$functionString)"
}
}
}