This is an automated email from the ASF dual-hosted git repository.

agrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new ca2e61100 feat: Add plan conversion statistics to extended explain 
info (#2412)
ca2e61100 is described below

commit ca2e61100586da05f19fadd09b362c08243e3b37
Author: Andy Grove <[email protected]>
AuthorDate: Wed Oct 1 14:10:10 2025 -0600

    feat: Add plan conversion statistics to extended explain info (#2412)
---
 .../main/scala/org/apache/comet/CometConf.scala    |  6 +-
 docs/source/user-guide/latest/configs.md           |  2 +-
 .../org/apache/comet/ExtendedExplainInfo.scala     | 68 +++++++++++++++++++---
 .../org/apache/comet/exec/CometExecSuite.scala     |  5 ++
 4 files changed, 69 insertions(+), 12 deletions(-)

diff --git a/common/src/main/scala/org/apache/comet/CometConf.scala 
b/common/src/main/scala/org/apache/comet/CometConf.scala
index 15c160ed6..c2a5d0582 100644
--- a/common/src/main/scala/org/apache/comet/CometConf.scala
+++ b/common/src/main/scala/org/apache/comet/CometConf.scala
@@ -457,8 +457,10 @@ object CometConf extends ShimCometConf {
   val COMET_EXPLAIN_VERBOSE_ENABLED: ConfigEntry[Boolean] =
     conf("spark.comet.explain.verbose.enabled")
       .doc(
-        "When this setting is enabled, Comet will provide a verbose tree 
representation of " +
-          "the extended information.")
+        "When this setting is enabled, Comet's extended explain output will 
provide the full " +
+          "query plan annotated with fallback reasons as well as a summary of 
how much of " +
+          "the plan was accelerated by Comet. When this setting is disabled, a 
list of fallback " +
+          "reasons will be provided instead.")
       .booleanConf
       .createWithDefault(false)
 
diff --git a/docs/source/user-guide/latest/configs.md 
b/docs/source/user-guide/latest/configs.md
index 410386e95..bebca3c44 100644
--- a/docs/source/user-guide/latest/configs.md
+++ b/docs/source/user-guide/latest/configs.md
@@ -63,7 +63,7 @@ Comet provides the following configuration settings.
 | spark.comet.exec.union.enabled | Whether to enable union by default. | true |
 | spark.comet.exec.window.enabled | Whether to enable window by default. | 
true |
 | spark.comet.explain.native.enabled | When this setting is enabled, Comet 
will provide a tree representation of the native query plan before execution 
and again after execution, with metrics. | false |
-| spark.comet.explain.verbose.enabled | When this setting is enabled, Comet 
will provide a verbose tree representation of the extended information. | false 
|
+| spark.comet.explain.verbose.enabled | When this setting is enabled, Comet's 
extended explain output will provide the full query plan annotated with 
fallback reasons as well as a summary of how much of the plan was accelerated 
by Comet. When this setting is disabled, a list of fallback reasons will be 
provided instead. | false |
 | spark.comet.explainFallback.enabled | When this setting is enabled, Comet 
will provide logging explaining the reason(s) why a query stage cannot be 
executed natively. Set this to false to reduce the amount of logging. | false |
 | spark.comet.expression.allowIncompatible | Comet is not currently fully 
compatible with Spark for all expressions. Set this config to true to allow 
them anyway. For more information, refer to the Comet Compatibility Guide 
(https://datafusion.apache.org/comet/user-guide/compatibility.html). | false |
 | spark.comet.logFallbackReasons.enabled | When this setting is enabled, Comet 
will log warnings for all fallback reasons. | false |
diff --git a/spark/src/main/scala/org/apache/comet/ExtendedExplainInfo.scala 
b/spark/src/main/scala/org/apache/comet/ExtendedExplainInfo.scala
index b5b8a5302..1e514956d 100644
--- a/spark/src/main/scala/org/apache/comet/ExtendedExplainInfo.scala
+++ b/spark/src/main/scala/org/apache/comet/ExtendedExplainInfo.scala
@@ -23,8 +23,9 @@ import scala.collection.mutable
 
 import org.apache.spark.sql.ExtendedExplainGenerator
 import org.apache.spark.sql.catalyst.trees.{TreeNode, TreeNodeTag}
-import org.apache.spark.sql.execution.{InputAdapter, SparkPlan, 
WholeStageCodegenExec}
-import org.apache.spark.sql.execution.adaptive.{AdaptiveSparkPlanExec, 
QueryStageExec}
+import org.apache.spark.sql.comet.{CometColumnarToRowExec, CometPlan, 
CometSparkToColumnarExec}
+import org.apache.spark.sql.execution.{ColumnarToRowExec, InputAdapter, 
RowToColumnarExec, SparkPlan, WholeStageCodegenExec}
+import org.apache.spark.sql.execution.adaptive.{AdaptiveSparkPlanExec, 
AQEShuffleReadExec, QueryStageExec}
 import org.apache.spark.sql.execution.exchange.ReusedExchangeExec
 
 import org.apache.comet.CometExplainInfo.getActualPlan
@@ -82,9 +83,18 @@ class ExtendedExplainInfo extends ExtendedExplainGenerator {
   // generates the extended info in a verbose manner, printing each node along 
with the
   // extended information in a tree display
   def generateVerboseExtendedInfo(plan: SparkPlan): String = {
+    val planStats = new CometCoverageStats()
     val outString = new StringBuilder()
-    generateTreeString(getActualPlan(plan), 0, Seq(), 0, outString)
-    outString.toString()
+    generateTreeString(getActualPlan(plan), 0, Seq(), 0, outString, planStats)
+    s"${outString.toString()}\n$planStats"
+  }
+
+  /** Get the coverage statistics without the full plan */
+  def generateCoverageInfo(plan: SparkPlan): String = {
+    val planStats = new CometCoverageStats()
+    val outString = new StringBuilder()
+    generateTreeString(getActualPlan(plan), 0, Seq(), 0, outString, planStats)
+    planStats.toString()
   }
 
   // Simplified generateTreeString from Spark TreeNode. Appends explain info 
to the node if any
@@ -93,7 +103,22 @@ class ExtendedExplainInfo extends ExtendedExplainGenerator {
       depth: Int,
       lastChildren: Seq[Boolean],
       indent: Int,
-      outString: StringBuilder): Unit = {
+      outString: StringBuilder,
+      planStats: CometCoverageStats): Unit = {
+
+    node match {
+      case _: AdaptiveSparkPlanExec | _: InputAdapter | _: QueryStageExec |
+          _: WholeStageCodegenExec | _: ReusedExchangeExec | _: 
AQEShuffleReadExec =>
+      // ignore
+      case _: RowToColumnarExec | _: ColumnarToRowExec | _: 
CometColumnarToRowExec |
+          _: CometSparkToColumnarExec =>
+        planStats.transitions += 1
+      case _: CometPlan =>
+        planStats.cometOperators += 1
+      case _ =>
+        planStats.sparkOperators += 1
+    }
+
     outString.append("   " * indent)
     if (depth > 0) {
       lastChildren.init.foreach { isLast =>
@@ -120,7 +145,8 @@ class ExtendedExplainInfo extends ExtendedExplainGenerator {
             depth + 2,
             lastChildren :+ node.children.isEmpty :+ false,
             indent,
-            outString)
+            outString,
+            planStats)
         case _ =>
       }
       generateTreeString(
@@ -128,7 +154,8 @@ class ExtendedExplainInfo extends ExtendedExplainGenerator {
         depth + 2,
         lastChildren :+ node.children.isEmpty :+ true,
         indent,
-        outString)
+        outString,
+        planStats)
     }
     if (node.children.nonEmpty) {
       node.children.init.foreach {
@@ -138,18 +165,40 @@ class ExtendedExplainInfo extends 
ExtendedExplainGenerator {
             depth + 1,
             lastChildren :+ false,
             indent,
-            outString)
+            outString,
+            planStats)
         case _ =>
       }
       node.children.last match {
         case c @ (_: TreeNode[_]) =>
-          generateTreeString(getActualPlan(c), depth + 1, lastChildren :+ 
true, indent, outString)
+          generateTreeString(
+            getActualPlan(c),
+            depth + 1,
+            lastChildren :+ true,
+            indent,
+            outString,
+            planStats)
         case _ =>
       }
     }
   }
 }
 
+class CometCoverageStats {
+  var sparkOperators: Int = 0
+  var cometOperators: Int = 0
+  var transitions: Int = 0
+
+  override def toString(): String = {
+    val eligible = sparkOperators + cometOperators
+    val converted =
+      if (eligible == 0) 0.0 else cometOperators.toDouble / eligible * 100.0
+    s"Comet accelerated $cometOperators out of $eligible " +
+      s"eligible operators (${converted.toInt}%). " +
+      s"Final plan contains $transitions transitions between Spark and Comet."
+  }
+}
+
 object CometExplainInfo {
   val EXTENSION_INFO = new TreeNodeTag[Set[String]]("CometExtensionInfo")
 
@@ -162,6 +211,7 @@ object CometExplainInfo {
       case p: ReusedExchangeExec => getActualPlan(p.child)
       case p => p
     }
+
   }
 
 }
diff --git a/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala 
b/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala
index ca76234c8..04203f754 100644
--- a/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala
+++ b/spark/src/test/scala/org/apache/comet/exec/CometExecSuite.scala
@@ -118,6 +118,11 @@ class CometExecSuite extends CometTestBase {
           val (_, cometPlan) = checkSparkAnswer(df)
           val infos = new ExtendedExplainInfo().generateExtendedInfo(cometPlan)
           assert(infos.contains("Dynamic Partition Pruning is not supported"))
+
+          withSQLConf(CometConf.COMET_EXPLAIN_VERBOSE_ENABLED.key -> "true") {
+            val extendedExplain = new 
ExtendedExplainInfo().generateExtendedInfo(cometPlan)
+            assert(extendedExplain.contains("Comet accelerated"))
+          }
         }
       }
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to