[ASTERIXDB-3356][RT] Add min/max cardinality to plan

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Add min and max cardinality from profile to plan
- Add test to check format of plan with profile annotations

Change-Id: Ibdacf4e6b156a3b6ef15b4420f4102c122f8af1d
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/18174
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Ian Maxon <imaxon@uci.edu>
Reviewed-by: Ian Maxon <imaxon@uci.edu>
Reviewed-by: Vijay Sarathy <vijay.sarathy@couchbase.com>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
index 9d62c52..e6b4662 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
@@ -831,9 +831,10 @@
         List<Parameter> newParams = setFormatInAccept(fmt) ? params
                 : upsertParam(params, QueryServiceRequestParameters.Parameter.FORMAT.str(), ParameterTypeEnum.STRING,
                         fmt.extension());
-
-        newParams = upsertParam(newParams, QueryServiceRequestParameters.Parameter.PLAN_FORMAT.str(),
-                ParameterTypeEnum.STRING, DEFAULT_PLAN_FORMAT);
+        String planFormatKey = QueryServiceRequestParameters.Parameter.PLAN_FORMAT.str();
+        if (!newParams.stream().anyMatch(p -> p.getName().equals(planFormatKey))) {
+            newParams = upsertParam(newParams, planFormatKey, ParameterTypeEnum.STRING, DEFAULT_PLAN_FORMAT);
+        }
         final Optional<String> maxReadsOptional = extractMaxResultReads(str);
         if (maxReadsOptional.isPresent()) {
             newParams = upsertParam(newParams, QueryServiceRequestParameters.Parameter.MAX_RESULT_READS.str(),
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/profiled.xml b/asterixdb/asterix-app/src/test/resources/runtimets/profiled.xml
index d3664d4..b3f222a 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/profiled.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/profiled.xml
@@ -45,6 +45,14 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="profile">
+      <compilation-unit name="plansleep">
+        <parameter name="profile" value="timings" type="string"/>
+        <parameter name="optimized-logical-plan" value="true" type="string"/>
+        <parameter name="plan-format" value="json" type="string"/>
+        <output-dir compare="Clean-JSON">plansleep</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="profile">
       <compilation-unit name="non-unary-subplan">
         <parameter name="profile" value="timings" type="string"/>
         <output-dir compare="Text">non-unary-subplan</output-dir>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.1.ddl.sqlpp
new file mode 100644
index 0000000..aedce7b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.1.ddl.sqlpp
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Description  : Testing that "off" is supplied in request parameter "profile".
+ * Expected Res : Success with expected result not having "profile" field.
+ */
+
+drop dataverse test if exists;
+create dataverse test;
+
+use test;
+
+create type test.AddressType as
+{
+  number : bigint,
+  street : string,
+  city : string
+};
+
+create type test.CustomerType as
+ closed {
+  cid : bigint,
+  name : string,
+  age : bigint?,
+  address : AddressType?,
+  lastorder : {
+      oid : bigint,
+      total : float
+  }
+};
+
+create type test.OrderType as
+{
+  oid : bigint,
+  cid : bigint,
+  orderstatus : string,
+  orderpriority : string,
+  clerk : string,
+  total : float
+};
+
+create dataset Customers(CustomerType) primary key cid;
+
+create  dataset Orders(OrderType) primary key oid;
+
+create dataset Customers2(CustomerType) primary key cid;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.2.update.sqlpp
new file mode 100644
index 0000000..07af22e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.2.update.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use test;
+
+load dataset Customers using localfs
+  ((`path`=`asterix_nc1://data/custord-tiny/customer-tiny-neg.adm`),
+  (`format`=`adm`));
+
+load dataset Orders using localfs
+  ((`path`=`asterix_nc1://data/custord-tiny/order-tiny.adm`),
+  (`format`=`adm`));
+
+load dataset Customers2 using localfs
+  ((`path`=`asterix_nc1://data/custord-tiny/customer-tiny.adm`),
+  (`format`=`adm`));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.3.plans.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.3.plans.sqlpp
new file mode 100644
index 0000000..6c35376
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.3.plans.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+-- compareunorderedarray=true
+USE test;
+
+SELECT count(*) AS customers, city
+FROM Customers c
+WHERE c.age <65
+GROUP BY c.address.city
+ORDER BY sleep(city,1700);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.90.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.90.ddl.sqlpp
new file mode 100644
index 0000000..f12a2b7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/profile/plansleep/plansleep.90.ddl.sqlpp
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+drop dataverse test;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/profile/plansleep/sleep.3.regexjson b/asterixdb/asterix-app/src/test/resources/runtimets/results/profile/plansleep/sleep.3.regexjson
new file mode 100644
index 0000000..111ded8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/profile/plansleep/sleep.3.regexjson
@@ -0,0 +1,390 @@
+{
+  "optimizedLogicalPlan": {
+    "operator": "distribute-result",
+    "expressions": [
+      "$$48"
+    ],
+    "operatorId": "1.1",
+    "runtime-id": "R{.+}",
+    "min-time": "R{[0-9.]+}",
+    "max-time": "R{[0-9.]+}",
+    "physical-operator": "DISTRIBUTE_RESULT",
+    "execution-mode": "PARTITIONED",
+    "optimizer-estimates": "R{.+}",
+    "inputs": [
+      {
+        "operator": "exchange",
+        "operatorId": "1.2",
+        "runtime-id": "R{.+}",
+        "physical-operator": "ONE_TO_ONE_EXCHANGE",
+        "execution-mode": "PARTITIONED",
+        "optimizer-estimates":"R{.+}",
+        "inputs": [
+          {
+            "operator": "project",
+            "variables": [
+              "$$48"
+            ],
+            "operatorId": "1.3",
+            "runtime-id": "R{.+}",
+            "min-time": "R{[0-9.]+}",
+            "max-time": "R{[0-9.]+}",
+            "min-cardinality": 3,
+            "max-cardinality": 3,
+            "physical-operator": "STREAM_PROJECT",
+            "execution-mode": "PARTITIONED",
+            "optimizer-estimates": "R{.+}",
+            "inputs": [
+              {
+                "operator": "exchange",
+                "operatorId": "1.4",
+                "runtime-id": "R{.+}",
+                "min-time": "R{[0-9.]+}",
+                "max-time": "R{[0-9.]+}",
+                "physical-operator": "SORT_MERGE_EXCHANGE [$$49(ASC) ]",
+                "execution-mode": "PARTITIONED",
+                "optimizer-estimates": "R{.+}",
+                "inputs": [
+                  {
+                    "operator": "order",
+                    "order-by-list": [
+                      {
+                        "order": "ASC",
+                        "expression": "$$49"
+                      }
+                    ],
+                    "operatorId": "1.5",
+                    "runtime-id": "R{.+}",
+                    "min-time": "R{[0-9.]+}",
+                    "max-time": "R{[0-9.]+}",
+                    "physical-operator": "STABLE_SORT [$$49(ASC)]",
+                    "execution-mode": "PARTITIONED",
+                    "optimizer-estimates": "R{.+}",
+                    "inputs": [
+                      {
+                        "operator": "exchange",
+                        "operatorId": "1.6",
+                        "runtime-id": "R{.+}",
+                        "physical-operator": "ONE_TO_ONE_EXCHANGE",
+                        "execution-mode": "PARTITIONED",
+                        "optimizer-estimates": "R{.+}",
+                        "inputs": [
+                          {
+                            "operator": "project",
+                            "variables": [
+                              "$$48",
+                              "$$49"
+                            ],
+                            "operatorId": "1.7",
+                            "runtime-id": "R{.+}",
+                            "min-time": "R{[0-9.]+}",
+                            "max-time": "R{[0-9.]+}",
+                            "min-cardinality": 3,
+                            "max-cardinality": 3,
+                            "physical-operator": "STREAM_PROJECT",
+                            "execution-mode": "PARTITIONED",
+                            "optimizer-estimates": "R{.+}",
+                            "inputs": [
+                              {
+                                "operator": "assign",
+                                "variables": [
+                                  "$$49"
+                                ],
+                                "expressions": [
+                                  "sleep($$city, 1700)"
+                                ],
+                                "operatorId": "1.8",
+                                "runtime-id": "R{.+}",
+                                "min-time": "R{5.+}",
+                                "max-time": "R{5.+}",
+                                "min-cardinality": 3,
+                                "max-cardinality": 3,
+                                "physical-operator": "ASSIGN",
+                                "execution-mode": "PARTITIONED",
+                                "optimizer-estimates": "R{.+}",
+                                "inputs": [
+                                  {
+                                    "operator": "project",
+                                    "variables": [
+                                      "$$city",
+                                      "$$48"
+                                    ],
+                                    "operatorId": "1.9",
+                                    "runtime-id": "R{.+}",
+                                    "min-time": "R{[0-9.]+}",
+                                    "max-time": "R{[0-9.]+}",
+                                    "min-cardinality": 3,
+                                    "max-cardinality": 3,
+                                    "physical-operator": "STREAM_PROJECT",
+                                    "execution-mode": "PARTITIONED",
+                                    "optimizer-estimates": "R{.+}",
+                                    "inputs": [
+                                      {
+                                        "operator": "assign",
+                                        "variables": [
+                                          "$$48"
+                                        ],
+                                        "expressions": [
+                                          "{\"customers\": $$52, \"city\": $$city}"
+                                        ],
+                                        "operatorId": "1.10",
+                                        "runtime-id": "R{.+}",
+                                        "min-time": "R{[0-9.]+}",
+                                        "max-time": "R{[0-9.]+}",
+                                        "min-cardinality": 3,
+                                        "max-cardinality": 3,
+                                        "physical-operator": "ASSIGN",
+                                        "execution-mode": "PARTITIONED",
+                                        "optimizer-estimates": "R{.+}",
+                                        "inputs": [
+                                          {
+                                            "operator": "exchange",
+                                            "operatorId": "1.11",
+                                            "runtime-id": "R{.+}",
+                                            "physical-operator": "ONE_TO_ONE_EXCHANGE",
+                                            "execution-mode": "PARTITIONED",
+                                            "optimizer-estimates": "R{.+}",
+                                            "inputs": [
+                                              {
+                                                "operator": "group-by",
+                                                "group-by-list": [
+                                                  {
+                                                    "variable": "$$city",
+                                                    "expression": "$$56"
+                                                  }
+                                                ],
+                                                "subplan": [
+                                                  {
+                                                    "operator": "aggregate",
+                                                    "variables": [
+                                                      "$$52"
+                                                    ],
+                                                    "expressions": [
+                                                      "agg-sql-sum($$55)"
+                                                    ],
+                                                    "operatorId": "1.11.1",
+                                                    "physical-operator": "AGGREGATE",
+                                                    "execution-mode": "LOCAL",
+                                                    "inputs": [
+                                                      {
+                                                        "operator": "nested-tuple-source",
+                                                        "operatorId": "1.11.2",
+                                                        "physical-operator": "NESTED_TUPLE_SOURCE",
+                                                        "execution-mode": "LOCAL"
+                                                      }
+                                                    ]
+                                                  }
+                                                ],
+                                                "operatorId": "1.12",
+                                                "runtime-id": "R{.+}",
+                                                "min-time": "R{[0-9.]+}",
+                                                "max-time": "R{[0-9.]+}",
+                                                "min-cardinality": 3,
+                                                "max-cardinality": 3,
+                                                "physical-operator": "SORT_GROUP_BY[$$56]",
+                                                "execution-mode": "PARTITIONED",
+                                                "optimizer-estimates": "R{.+}",
+                                                "inputs": [
+                                                  {
+                                                    "operator": "exchange",
+                                                    "operatorId": "1.13",
+                                                    "runtime-id": "R{.+}",
+                                                    "min-time": "R{[0-9.]+}",
+                                                    "max-time": "R{[0-9.]+}",
+                                                    "physical-operator": "HASH_PARTITION_EXCHANGE [$$56]",
+                                                    "execution-mode": "PARTITIONED",
+                                                    "optimizer-estimates": "R{.+}",
+                                                    "inputs": [
+                                                      {
+                                                        "operator": "group-by",
+                                                        "group-by-list": [
+                                                          {
+                                                            "variable": "$$56",
+                                                            "expression": "$$50"
+                                                          }
+                                                        ],
+                                                        "subplan": [
+                                                          {
+                                                            "operator": "aggregate",
+                                                            "variables": [
+                                                              "$$55"
+                                                            ],
+                                                            "expressions": [
+                                                              "agg-sql-count(1)"
+                                                            ],
+                                                            "operatorId": "1.13.1",
+                                                            "physical-operator": "AGGREGATE",
+                                                            "execution-mode": "LOCAL",
+                                                            "inputs": [
+                                                              {
+                                                                "operator": "nested-tuple-source",
+                                                                "operatorId": "1.13.2",
+                                                                "physical-operator": "NESTED_TUPLE_SOURCE",
+                                                                "execution-mode": "LOCAL"
+                                                              }
+                                                            ]
+                                                          }
+                                                        ],
+                                                        "operatorId": "1.14",
+                                                        "runtime-id": "R{.+}",
+                                                        "min-time": "R{[0-9.]+}",
+                                                        "max-time": "R{[0-9.]+}",
+                                                        "physical-operator": "SORT_GROUP_BY[$$50]",
+                                                        "execution-mode": "PARTITIONED",
+                                                        "optimizer-estimates": "R{.+}",
+                                                        "inputs": [
+                                                          {
+                                                            "operator": "exchange",
+                                                            "operatorId": "1.15",
+                                                            "runtime-id": "R{.+}",
+                                                            "physical-operator": "ONE_TO_ONE_EXCHANGE",
+                                                            "execution-mode": "PARTITIONED",
+                                                            "optimizer-estimates": "R{.+}",
+                                                            "inputs": [
+                                                              {
+                                                                "operator": "project",
+                                                                "variables": [
+                                                                  "$$50"
+                                                                ],
+                                                                "operatorId": "1.16",
+                                                                "runtime-id": "R{.+}",
+                                                                "min-time": "R{[0-9.]+}",
+                                                                "max-time": "R{[0-9.]+}",
+                                                                "min-cardinality": 5,
+                                                                "max-cardinality": 5,
+                                                                "physical-operator": "STREAM_PROJECT",
+                                                                "execution-mode": "PARTITIONED",
+                                                                "optimizer-estimates": "R{.+}",
+                                                                "inputs": [
+                                                                  {
+                                                                    "operator": "assign",
+                                                                    "variables": [
+                                                                      "$$50"
+                                                                    ],
+                                                                    "expressions": [
+                                                                      "$$c.getField(3).getField(2)"
+                                                                    ],
+                                                                    "operatorId": "1.17",
+                                                                    "runtime-id": "R{.+}",
+                                                                    "min-time": "R{[0-9.]+}",
+                                                                    "max-time": "R{[0-9.]+}",
+                                                                    "min-cardinality": 5,
+                                                                    "max-cardinality": 5,
+                                                                    "physical-operator": "ASSIGN",
+                                                                    "execution-mode": "PARTITIONED",
+                                                                    "optimizer-estimates": "R{.+}",
+                                                                    "inputs": [
+                                                                      {
+                                                                        "operator": "select",
+                                                                        "condition": "lt($$c.getField(2), 65)",
+                                                                        "operatorId": "1.18",
+                                                                        "runtime-id": "R{.+}",
+                                                                        "min-time": "R{[0-9.]+}",
+                                                                        "max-time": "R{[0-9.]+}",
+                                                                        "min-cardinality": 5,
+                                                                        "max-cardinality": 5,
+                                                                        "physical-operator": "STREAM_SELECT",
+                                                                        "execution-mode": "PARTITIONED",
+                                                                        "optimizer-estimates": "R{.+}",
+                                                                        "inputs": [
+                                                                          {
+                                                                            "operator": "project",
+                                                                            "variables": [
+                                                                              "$$c"
+                                                                            ],
+                                                                            "operatorId": "1.19",
+                                                                            "runtime-id": "R{.+}",
+                                                                            "min-time": "R{[0-9.]+}",
+                                                                            "max-time": "R{[0-9.]+}",
+                                                                            "min-cardinality": 10,
+                                                                            "max-cardinality": 10,
+                                                                            "physical-operator": "STREAM_PROJECT",
+                                                                            "execution-mode": "PARTITIONED",
+                                                                            "optimizer-estimates": "R{.+}",
+                                                                            "inputs": [
+                                                                              {
+                                                                                "operator": "exchange",
+                                                                                "operatorId": "1.20",
+                                                                                "runtime-id": "R{.+}",
+                                                                                "physical-operator": "ONE_TO_ONE_EXCHANGE",
+                                                                                "execution-mode": "PARTITIONED",
+                                                                                "optimizer-estimates": "R{.+}",
+                                                                                "inputs": [
+                                                                                  {
+                                                                                    "operator": "data-scan",
+                                                                                    "variables": [
+                                                                                      "$$51",
+                                                                                      "$$c"
+                                                                                    ],
+                                                                                    "data-source": "test.Customers",
+                                                                                    "operatorId": "1.21",
+                                                                                    "runtime-id": "R{.+}",
+                                                                                    "min-time": "R{[0-9.]+}",
+                                                                                    "max-time": "R{[0-9.]+}",
+                                                                                    "min-cardinality": 10,
+                                                                                    "max-cardinality": 10,
+                                                                                    "physical-operator": "DATASOURCE_SCAN",
+                                                                                    "execution-mode": "PARTITIONED",
+                                                                                    "optimizer-estimates": "R{.+}",
+                                                                                    "inputs": [
+                                                                                      {
+                                                                                        "operator": "exchange",
+                                                                                        "operatorId": "1.22",
+                                                                                        "runtime-id": "R{.+}",
+                                                                                        "physical-operator": "ONE_TO_ONE_EXCHANGE",
+                                                                                        "execution-mode": "PARTITIONED",
+                                                                                        "optimizer-estimates": "R{.+}",
+                                                                                        "inputs": [
+                                                                                          {
+                                                                                            "operator": "empty-tuple-source",
+                                                                                            "operatorId": "1.23",
+                                                                                            "runtime-id": "R{.+}",
+                                                                                            "physical-operator": "EMPTY_TUPLE_SOURCE",
+                                                                                            "execution-mode": "PARTITIONED",
+                                                                                            "optimizer-estimates": "R{.+}"
+                                                                                          }
+                                                                                        ]
+                                                                                      }
+                                                                                    ]
+                                                                                  }
+                                                                                ]
+                                                                              }
+                                                                            ]
+                                                                          }
+                                                                        ]
+                                                                      }
+                                                                    ]
+                                                                  }
+                                                                ]
+                                                              }
+                                                            ]
+                                                          }
+                                                        ]
+                                                      }
+                                                    ]
+                                                  }
+                                                ]
+                                              }
+                                            ]
+                                          }
+                                        ]
+                                      }
+                                    ]
+                                  }
+                                ]
+                              }
+                            ]
+                          }
+                        ]
+                      }
+                    ]
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
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 d1a356c..36c33e6 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
@@ -228,19 +228,32 @@
     }
 
     private class OperatorProfile {
-        Map<String, Pair<Double, Double>> activities;
+        Map<String, Pair<Double, Double>> activityTimes;
+        Map<String, Pair<Long, Long>> activityCards;
 
         OperatorProfile() {
-            activities = new HashMap<>();
+            activityTimes = new HashMap<>();
+            activityCards = new HashMap<>();
+        }
+
+        void updateOperator(String extendedOpId, double time, long cardinality) {
+            updateMinMax(time, extendedOpId, activityTimes);
+            if (cardinality > 0) {
+                updateMinMax(cardinality, extendedOpId, activityCards);
+            }
         }
 
         void updateOperator(String extendedOpId, double time) {
-            Pair<Double, Double> times = activities.computeIfAbsent(extendedOpId, i -> new Pair(time, time));
-            if (times.getFirst() > time) {
-                times.setFirst(time);
+            updateMinMax(time, extendedOpId, activityTimes);
+        }
+
+        private <T extends Comparable<T>> void updateMinMax(T comp, String id, Map<String, Pair<T, T>> opMap) {
+            Pair<T, T> times = opMap.computeIfAbsent(id, i -> new Pair(comp, comp));
+            if (times.getFirst().compareTo(comp) > 0) {
+                times.setFirst(comp);
             }
-            if (times.getSecond() < time) {
-                times.setSecond(time);
+            if (times.getSecond().compareTo(comp) < 0) {
+                times.setSecond(comp);
             }
         }
     }
@@ -257,6 +270,11 @@
                 for (JsonNode counters : task.get("counters")) {
                     OperatorProfile info = profiledOps.computeIfAbsent(counters.get("runtime-id").asText(),
                             i -> new OperatorProfile());
+                    JsonNode card = counters.get("cardinality-out");
+                    if (card != null) {
+                        info.updateOperator(acIdFromName(counters.get("name").asText()).getLocalId(),
+                                counters.get("run-time").asDouble(), counters.get("cardinality-out").asLong(-1));
+                    }
                     info.updateOperator(acIdFromName(counters.get("name").asText()).getLocalId(),
                             counters.get("run-time").asDouble());
                 }
@@ -343,16 +361,26 @@
                 jsonGenerator.writeStringField("runtime-id", od);
                 OperatorProfile info = profile.get(od);
                 if (info != null) {
-                    if (info.activities.size() == 1) {
-                        Pair<Double, Double> minMax = info.activities.values().iterator().next();
+                    if (info.activityTimes.size() == 1) {
+                        Pair<Double, Double> minMax = info.activityTimes.values().iterator().next();
                         jsonGenerator.writeNumberField("min-time", minMax.first);
                         jsonGenerator.writeNumberField("max-time", minMax.second);
+                        if (info.activityCards.size() > 0) {
+                            Pair<Long, Long> minMaxCard = info.activityCards.values().iterator().next();
+                            jsonGenerator.writeNumberField("min-cardinality", minMaxCard.first);
+                            jsonGenerator.writeNumberField("max-cardinality", minMaxCard.second);
+                        }
                     } else {
                         jsonGenerator.writeObjectFieldStart("times");
-                        for (Map.Entry<String, Pair<Double, Double>> ac : info.activities.entrySet()) {
-                            jsonGenerator.writeObjectFieldStart(ac.getKey());
-                            jsonGenerator.writeNumberField("min-time", ac.getValue().first);
-                            jsonGenerator.writeNumberField("max-time", ac.getValue().second);
+                        for (String acId : info.activityTimes.keySet()) {
+                            jsonGenerator.writeObjectFieldStart(acId);
+                            jsonGenerator.writeNumberField("min-time", info.activityTimes.get(acId).first);
+                            jsonGenerator.writeNumberField("max-time", info.activityTimes.get(acId).second);
+                            Pair<Long, Long> cards = info.activityCards.get(acId);
+                            if (cards != null) {
+                                jsonGenerator.writeNumberField("min-cardinality", info.activityCards.get(acId).first);
+                                jsonGenerator.writeNumberField("max-cardinality", info.activityCards.get(acId).second);
+                            }
                             jsonGenerator.writeEndObject();
                         }
                         jsonGenerator.writeEndObject();