You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by mb...@apache.org on 2023/01/30 03:27:15 UTC

[asterixdb] 26/30: [ASTERIXDB-3094][COMP] Add cost and cardinality to explain

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

mblow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git

commit 39d7562a593cae1afbc5496383e207f7bb4b08c5
Author: Vijay Sarathy <vi...@couchbase.com>
AuthorDate: Thu Dec 15 09:50:57 2022 -0800

    [ASTERIXDB-3094][COMP] Add cost and cardinality to explain
    
    Backport changes:
    - Compensate for lack of add'l operator stats change in this branch
    
    Change-Id: I7b4a6c78dca3326f070e1dda888d774dc6100c0b
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17301
    Integration-Tests: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Tested-by: Jenkins <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Ali Alsuliman <al...@gmail.com>
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17341
    Reviewed-by: Michael Blow <mb...@apache.org>
    Tested-by: Michael Blow <mb...@apache.org>
---
 .../optimizer/rules/cbo/EnumerateJoinsRule.java    |  2 +-
 .../rules/cbo/EstimatedCostComputationVisitor.java | 14 ++--
 .../apache/asterix/api/common/APIFramework.java    | 35 ++++++--
 .../api/compileonly/compileonly.2.regexjson        |  1 +
 .../core/algebra/plan/ALogicalPlanImpl.java        |  2 +-
 .../core/algebra/plan/PlanStabilityVerifier.java   |  2 +-
 .../algebra/prettyprint/IPlanPrettyPrinter.java    |  7 +-
 .../LogicalOperatorPrettyPrintVisitor.java         |  7 +-
 .../LogicalOperatorPrettyPrintVisitorJson.java     | 92 +++++++++++++++++-----
 .../core/rewriter/base/HeuristicOptimizer.java     |  2 +-
 .../core/algebra/plan/PlanVerifierTestBase.java    |  2 +-
 11 files changed, 120 insertions(+), 46 deletions(-)

diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java
index dc722e11f8..20b4d82cbd 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EnumerateJoinsRule.java
@@ -513,7 +513,7 @@ public class EnumerateJoinsRule implements IAlgebraicRewriteRule {
             throws AlgebricksException {
         if (LOGGER.isTraceEnabled()) {
             pp.reset();
-            pp.printOperator(op, true);
+            pp.printOperator(op, true, false);
             LOGGER.trace("---------------------------- {}\n{}\n----------------------------", text, pp);
         }
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EstimatedCostComputationVisitor.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EstimatedCostComputationVisitor.java
index 9b9fd28a82..f9f34bf0d7 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EstimatedCostComputationVisitor.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/cbo/EstimatedCostComputationVisitor.java
@@ -86,7 +86,7 @@ public class EstimatedCostComputationVisitor implements ILogicalOperatorVisitor<
     public Pair<Double, Double> visitEmptyTupleSourceOperator(EmptyTupleSourceOperator op, Double arg)
             throws AlgebricksException {
         // Empty tuple source operator sends an empty tuple to downstream operators.
-        return new Pair<>(1.0, 1.0);
+        return new Pair<>(0.0, 0.0);
     }
 
     @Override
@@ -220,7 +220,7 @@ public class EstimatedCostComputationVisitor implements ILogicalOperatorVisitor<
     @Override
     public Pair<Double, Double> visitDataScanOperator(DataSourceScanOperator op, Double arg)
             throws AlgebricksException {
-        Pair<Double, Double> cardCost = new Pair<>(1.0, 1.0);
+        Pair<Double, Double> cardCost = new Pair<>(0.0, 0.0);
 
         for (Map.Entry<String, Object> anno : op.getAnnotations().entrySet()) {
             if (anno.getValue() != null && anno.getKey().equals(OperatorAnnotations.OP_INPUT_CARDINALITY)) {
@@ -250,7 +250,7 @@ public class EstimatedCostComputationVisitor implements ILogicalOperatorVisitor<
             op.getAnnotations().put(OperatorAnnotations.OP_COST_LOCAL, exchCost);
             op.getAnnotations().put(OperatorAnnotations.OP_COST_TOTAL, exchCost + cardCost.getSecond());
         } else {
-            op.getAnnotations().put(OperatorAnnotations.OP_COST_LOCAL, cardCost.getSecond());
+            op.getAnnotations().put(OperatorAnnotations.OP_COST_LOCAL, 0.0);
             op.getAnnotations().put(OperatorAnnotations.OP_COST_TOTAL, cardCost.getSecond());
         }
         op.getAnnotations().put(OperatorAnnotations.OP_OUTPUT_CARDINALITY, cardCost.getFirst());
@@ -305,12 +305,12 @@ public class EstimatedCostComputationVisitor implements ILogicalOperatorVisitor<
     private static Pair<Double, Double> annotate(EstimatedCostComputationVisitor visitor, ILogicalOperator op,
             Double arg) throws AlgebricksException {
         if (op.getInputs().isEmpty()) {
-            return new Pair<>(1.0, 1.0);
+            return new Pair<>(0.0, 0.0);
         }
         Pair<Double, Double> cardCost = op.getInputs().get(0).getValue().accept(visitor, arg);
         op.getAnnotations().put(OperatorAnnotations.OP_OUTPUT_CARDINALITY, cardCost.getFirst());
         op.getAnnotations().put(OperatorAnnotations.OP_COST_TOTAL, cardCost.getSecond());
-        op.getAnnotations().put(OperatorAnnotations.OP_COST_LOCAL, cardCost.getSecond());
+        op.getAnnotations().put(OperatorAnnotations.OP_COST_LOCAL, 0.0);
         return cardCost;
     }
 
@@ -322,9 +322,9 @@ public class EstimatedCostComputationVisitor implements ILogicalOperatorVisitor<
         return operator.getInputs().get(0).getValue().accept(this, arg);
     }
 
-    // Visits an inner join operator, particularly, deals with the case the join is a cartesian product.
+    // Visits an inner join operator.
     private Pair<Double, Double> visitInnerJoin(InnerJoinOperator joinOperator, Double arg) throws AlgebricksException {
-        Pair<Double, Double> cardCost = new Pair<>(1.0, 1.0);
+        Pair<Double, Double> cardCost = new Pair<>(0.0, 0.0);
 
         ILogicalOperator left = joinOperator.getInputs().get(0).getValue();
         ILogicalOperator right = joinOperator.getInputs().get(1).getValue();
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
index fcac5b45a1..3409621d47 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
@@ -217,7 +217,7 @@ public class APIFramework {
 
         if ((isQuery || isLoad) && !conf.is(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS)
                 && conf.is(SessionConfig.OOB_LOGICAL_PLAN)) {
-            generateLogicalPlan(plan, output.config().getPlanFormat());
+            generateLogicalPlan(plan, output.config().getPlanFormat(), isExplainOnly);
         }
         ICcApplicationContext ccAppContext = metadataProvider.getApplicationContext();
         CompilerProperties compilerProperties = ccAppContext.getCompilerProperties();
@@ -277,7 +277,7 @@ public class APIFramework {
                     output.out().write(buf.toString());
                 } else {
                     if (isQuery || isLoad) {
-                        generateOptimizedLogicalPlan(plan, output.config().getPlanFormat());
+                        generateOptimizedLogicalPlan(plan, output.config().getPlanFormat(), isExplainOnly);
                     }
                 }
             }
@@ -311,6 +311,9 @@ public class APIFramework {
         }
 
         if (!conf.isGenerateJobSpec()) {
+            if (isQuery || isLoad) {
+                generateOptimizedLogicalPlan(plan, output.config().getPlanFormat(), isExplainOnly);
+            }
             return null;
         }
 
@@ -331,6 +334,21 @@ public class APIFramework {
                 spec.setRequiredClusterCapacity(jobRequiredCapacity);
             }
         }
+
+        if (conf.is(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN) || isExplainOnly) {
+            if (isQuery || isLoad) {
+                generateOptimizedLogicalPlan(plan, output.config().getPlanFormat(), isExplainOnly);
+            }
+        }
+
+        if (isExplainOnly) {
+            printPlanAsResult(metadataProvider, output, printer, printSignature);
+            if (!conf.is(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN)) {
+                executionPlans.setOptimizedLogicalPlan(null);
+            }
+            return null;
+        }
+
         if (isQuery && conf.is(SessionConfig.OOB_HYRACKS_JOB)) {
             generateJob(spec);
         }
@@ -516,13 +534,16 @@ public class APIFramework {
         }
     }
 
-    private void generateLogicalPlan(ILogicalPlan plan, SessionConfig.PlanFormat format) throws AlgebricksException {
-        executionPlans.setLogicalPlan(getPrettyPrintVisitor(format).printPlan(plan).toString());
+    private void generateLogicalPlan(ILogicalPlan plan, SessionConfig.PlanFormat format,
+            boolean printOptimizerEstimates) throws AlgebricksException {
+        executionPlans
+                .setLogicalPlan(getPrettyPrintVisitor(format).printPlan(plan, printOptimizerEstimates).toString());
     }
 
-    private void generateOptimizedLogicalPlan(ILogicalPlan plan, SessionConfig.PlanFormat format)
-            throws AlgebricksException {
-        executionPlans.setOptimizedLogicalPlan(getPrettyPrintVisitor(format).printPlan(plan).toString());
+    private void generateOptimizedLogicalPlan(ILogicalPlan plan, SessionConfig.PlanFormat format,
+            boolean printOptimizerEstimates) throws AlgebricksException {
+        executionPlans.setOptimizedLogicalPlan(
+                getPrettyPrintVisitor(format).printPlan(plan, printOptimizerEstimates).toString());
     }
 
     private void generateJob(JobSpecification spec) {
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.2.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.2.regexjson
index 40a764c29a..9856681ddc 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.2.regexjson
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/compileonly/compileonly.2.regexjson
@@ -4,6 +4,7 @@
 	  "expressions":"R{.*}",
 	  "operatorId":"R{.*}",
 	  "execution-mode":"R{.*}",
+	  "optimizer-estimates":{"cardinality":0.0,"op-cost":0.0,"total-cost":0.0},
 	  "inputs":"R{.*}"
   }
 }
\ No newline at end of file
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java
index b5b01a0b45..1bf257919a 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java
@@ -53,7 +53,7 @@ public class ALogicalPlanImpl implements ILogicalPlan {
     }
 
     public static String prettyPrintPlan(ILogicalPlan plan) throws AlgebricksException {
-        return PlanPrettyPrinter.createStringPlanPrettyPrinter().printPlan(plan).toString();
+        return PlanPrettyPrinter.createStringPlanPrettyPrinter().printPlan(plan, true).toString();
     }
 
     @Override
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanStabilityVerifier.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanStabilityVerifier.java
index 65aa64a978..d6ffe9ec9f 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanStabilityVerifier.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanStabilityVerifier.java
@@ -263,7 +263,7 @@ public final class PlanStabilityVerifier {
 
     static String printOperator(ILogicalOperator op, IPlanPrettyPrinter printer) {
         try {
-            return printer.reset().printOperator((AbstractLogicalOperator) op, false).toString();
+            return printer.reset().printOperator((AbstractLogicalOperator) op, false, false).toString();
         } catch (AlgebricksException e) {
             // shouldn't happen
             return op.toString();
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/IPlanPrettyPrinter.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/IPlanPrettyPrinter.java
index faf497633a..66d48d3f47 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/IPlanPrettyPrinter.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/IPlanPrettyPrinter.java
@@ -30,17 +30,18 @@ public interface IPlanPrettyPrinter {
 
     /** Prints the plan rooted at the operator argument. */
     default IPlanPrettyPrinter printOperator(AbstractLogicalOperator operator) throws AlgebricksException {
-        return printOperator(operator, true);
+        return printOperator(operator, true, false);
     }
 
     /** Prints given operator and optionally it's inputs */
-    IPlanPrettyPrinter printOperator(AbstractLogicalOperator operator, boolean printInputs) throws AlgebricksException;
+    IPlanPrettyPrinter printOperator(AbstractLogicalOperator operator, boolean printInputs,
+            boolean printOptimizerEstimates) throws AlgebricksException;
 
     /** Prints given expression */
     IPlanPrettyPrinter printExpression(ILogicalExpression expression) throws AlgebricksException;
 
     /** Prints the whole logical plan. */
-    IPlanPrettyPrinter printPlan(ILogicalPlan plan) throws AlgebricksException;
+    IPlanPrettyPrinter printPlan(ILogicalPlan plan, boolean printOptimizerEstimates) throws AlgebricksException;
 
     /** Resets the state of the pretty printer. */
     IPlanPrettyPrinter reset() throws AlgebricksException;
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
index 64abaee440..710e6e0a0b 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
@@ -90,14 +90,15 @@ public class LogicalOperatorPrettyPrintVisitor extends AbstractLogicalOperatorPr
     }
 
     @Override
-    public final IPlanPrettyPrinter printPlan(ILogicalPlan plan) throws AlgebricksException {
+    public final IPlanPrettyPrinter printPlan(ILogicalPlan plan, boolean printOptimizerEstimates)
+            throws AlgebricksException {
         printPlanImpl(plan, 0);
         return this;
     }
 
     @Override
-    public final IPlanPrettyPrinter printOperator(AbstractLogicalOperator op, boolean printInputs)
-            throws AlgebricksException {
+    public final IPlanPrettyPrinter printOperator(AbstractLogicalOperator op, boolean printInputs,
+            boolean printOptimizerEstimates) throws AlgebricksException {
         printOperatorImpl(op, 0, printInputs);
         return this;
     }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
index cf96c8397a..b1cdeb12d7 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
@@ -157,28 +157,30 @@ public class LogicalOperatorPrettyPrintVisitorJson extends AbstractLogicalOperat
     }
 
     @Override
-    public final IPlanPrettyPrinter printPlan(ILogicalPlan plan) throws AlgebricksException {
-        printPlanImpl(plan);
+    public final IPlanPrettyPrinter printPlan(ILogicalPlan plan, boolean printOptimizerEstimates)
+            throws AlgebricksException {
+        printPlanImpl(plan, printOptimizerEstimates);
         flushContentToWriter();
         return this;
     }
 
     @Override
-    public final IPlanPrettyPrinter printOperator(AbstractLogicalOperator op, boolean printInputs)
-            throws AlgebricksException {
-        printOperatorImpl(op, printInputs);
+
+    public final IPlanPrettyPrinter printOperator(AbstractLogicalOperator op, boolean printInputs,
+            boolean printOptimizerEstimates) throws AlgebricksException {
+        printOperatorImpl(op, printInputs, printOptimizerEstimates);
         flushContentToWriter();
         return this;
     }
 
-    private void printPlanImpl(ILogicalPlan plan) throws AlgebricksException {
+    private void printPlanImpl(ILogicalPlan plan, boolean printOptimizerEstimates) throws AlgebricksException {
         try {
             boolean writeArrayOfRoots = plan.getRoots().size() > 1;
             if (writeArrayOfRoots) {
                 jsonGenerator.writeStartArray();
             }
             for (Mutable<ILogicalOperator> root : plan.getRoots()) {
-                printOperatorImpl((AbstractLogicalOperator) root.getValue(), true);
+                printOperatorImpl((AbstractLogicalOperator) root.getValue(), true, printOptimizerEstimates);
             }
             if (writeArrayOfRoots) {
                 jsonGenerator.writeEndArray();
@@ -188,8 +190,11 @@ public class LogicalOperatorPrettyPrintVisitorJson extends AbstractLogicalOperat
         }
     }
 
-    private void printOperatorImpl(AbstractLogicalOperator op, boolean printInputs) throws AlgebricksException {
+    private void printOperatorImpl(AbstractLogicalOperator op, boolean printInputs, boolean printOptimizerEstimates)
+            throws AlgebricksException {
         try {
+            boolean nestPlanInPlanField = nestPlanInPlanField(op, printOptimizerEstimates);
+
             jsonGenerator.writeStartObject();
             op.accept(this, null);
             jsonGenerator.writeStringField("operatorId", idCounter.printOperatorId(op));
@@ -199,24 +204,71 @@ public class LogicalOperatorPrettyPrintVisitorJson extends AbstractLogicalOperat
             }
             jsonGenerator.writeStringField("execution-mode", op.getExecutionMode().toString());
 
+            generateCardCostFields(op);
+
+            if (printInputs && !op.getInputs().isEmpty()) {
+                jsonGenerator.writeArrayFieldStart("inputs");
+                for (Mutable<ILogicalOperator> k : op.getInputs()) {
+                    printOperatorImpl((AbstractLogicalOperator) k.getValue(), printInputs, printOptimizerEstimates);
+                }
+                jsonGenerator.writeEndArray();
+            }
+            jsonGenerator.writeEndObject();
+            if (nestPlanInPlanField) {
+                jsonGenerator.writeEndObject();
+            }
+        } catch (IOException e) {
+            throw AlgebricksException.create(ErrorCode.ERROR_PRINTING_PLAN, e, String.valueOf(e));
+        }
+    }
+
+    private boolean nestPlanInPlanField(AbstractLogicalOperator op, boolean printOptimizerEstimates)
+            throws IOException {
+        if (op.getOperatorTag() == LogicalOperatorTag.DISTRIBUTE_RESULT && printOptimizerEstimates) {
+            double cardinality = 0.0;
+            double cost = 0.0;
+            for (Map.Entry<String, Object> anno : op.getAnnotations().entrySet()) {
+                if (anno.getValue() != null && anno.getKey().equals(OperatorAnnotations.OP_OUTPUT_CARDINALITY)) {
+                    cardinality = (double) anno.getValue();
+                } else if (anno.getValue() != null && anno.getKey().equals(OperatorAnnotations.OP_COST_TOTAL)) {
+                    cost = (double) anno.getValue();
+                }
+            }
+
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeNumberField("cardinality", cardinality);
+            jsonGenerator.writeNumberField("cost", cost);
+            jsonGenerator.writeFieldName("plan");
+            return true;
+        }
+        return false;
+    }
+
+    private void generateCardCostFields(AbstractLogicalOperator op) throws AlgebricksException {
+        try {
+            double opCard = 0.0;
+            double opCostLocal = 0.0;
+            double opCostTotal = 0.0;
+
             for (Map.Entry<String, Object> anno : op.getAnnotations().entrySet()) {
                 Object annotationVal = anno.getValue();
                 if (annotationVal != null) {
                     String annotation = anno.getKey();
                     switch (annotation) {
                         case OperatorAnnotations.OP_COST_LOCAL:
+                            opCostLocal = (double) annotationVal;
+                            break;
                         case OperatorAnnotations.OP_COST_TOTAL:
-                            jsonGenerator.writeStringField(annotation.toLowerCase().replace('_', '-'),
-                                    annotationVal.toString());
+                            opCostTotal = (double) annotationVal;
                             break;
                         case OperatorAnnotations.OP_INPUT_CARDINALITY:
                             if (op.getOperatorTag() == LogicalOperatorTag.DATASOURCESCAN) {
-                                jsonGenerator.writeStringField(OP_CARDINALITY, annotationVal.toString());
+                                opCard = (double) annotationVal;
                             }
                             break;
                         case OperatorAnnotations.OP_OUTPUT_CARDINALITY:
                             if (op.getOperatorTag() != LogicalOperatorTag.DATASOURCESCAN) {
-                                jsonGenerator.writeStringField(OP_CARDINALITY, annotationVal.toString());
+                                opCard = (double) annotationVal;
                             }
                             break;
                         default:
@@ -224,14 +276,12 @@ public class LogicalOperatorPrettyPrintVisitorJson extends AbstractLogicalOperat
                     }
                 }
             }
-
-            if (printInputs && !op.getInputs().isEmpty()) {
-                jsonGenerator.writeArrayFieldStart("inputs");
-                for (Mutable<ILogicalOperator> k : op.getInputs()) {
-                    printOperatorImpl((AbstractLogicalOperator) k.getValue(), printInputs);
-                }
-                jsonGenerator.writeEndArray();
-            }
+            jsonGenerator.writeObjectFieldStart("optimizer-estimates");
+            jsonGenerator.writeNumberField(OP_CARDINALITY, opCard);
+            jsonGenerator.writeNumberField(OperatorAnnotations.OP_COST_LOCAL.toLowerCase().replace('_', '-'),
+                    opCostLocal);
+            jsonGenerator.writeNumberField(OperatorAnnotations.OP_COST_TOTAL.toLowerCase().replace('_', '-'),
+                    opCostTotal);
             jsonGenerator.writeEndObject();
         } catch (IOException e) {
             throw AlgebricksException.create(ErrorCode.ERROR_PRINTING_PLAN, e, String.valueOf(e));
@@ -796,7 +846,7 @@ public class LogicalOperatorPrettyPrintVisitorJson extends AbstractLogicalOperat
             jsonGenerator.writeArrayFieldStart("subplan");
             List<ILogicalPlan> nestedPlans = op.getNestedPlans();
             for (int i = 0, size = nestedPlans.size(); i < size; i++) {
-                printPlanImpl(nestedPlans.get(i));
+                printPlanImpl(nestedPlans.get(i), false);
             }
             jsonGenerator.writeEndArray();
             idCounter.previousPrefix();
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java
index a717295f0e..676b9c7fe9 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java
@@ -69,7 +69,7 @@ public class HeuristicOptimizer {
 
     private void logPlanAt(String name, Level lvl) throws AlgebricksException {
         if (AlgebricksConfig.ALGEBRICKS_LOGGER.isEnabled(lvl)) {
-            String planStr = context.getPrettyPrinter().reset().printPlan(plan).toString();
+            String planStr = context.getPrettyPrinter().reset().printPlan(plan, true).toString();
             AlgebricksConfig.ALGEBRICKS_LOGGER.log(lvl, name + ":\n" + LogRedactionUtil.userData(planStr));
         }
     }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/test/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanVerifierTestBase.java b/hyracks-fullstack/algebricks/algebricks-core/src/test/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanVerifierTestBase.java
index 65d64609d5..2f7bc9b097 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/test/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanVerifierTestBase.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/test/java/org/apache/hyracks/algebricks/core/algebra/plan/PlanVerifierTestBase.java
@@ -71,7 +71,7 @@ public abstract class PlanVerifierTestBase {
 
     String printOp(ILogicalOperator op) {
         try {
-            return planPrinter.reset().printOperator((AbstractLogicalOperator) op, false).toString();
+            return planPrinter.reset().printOperator((AbstractLogicalOperator) op, false, false).toString();
         } catch (AlgebricksException e) {
             throw new RuntimeException(e);
         }