Support multiple function args. And add ThermodynamicIndicator
diff --git a/oal-parser/src/main/java/org/apache/skywalking/oap/server/core/Indicators.java b/oal-parser/src/main/java/org/apache/skywalking/oap/server/core/Indicators.java
index e73f366..4be82c9 100644
--- a/oal-parser/src/main/java/org/apache/skywalking/oap/server/core/Indicators.java
+++ b/oal-parser/src/main/java/org/apache/skywalking/oap/server/core/Indicators.java
@@ -34,6 +34,7 @@
         REGISTER.put("p90", P90Indicator.class);
         REGISTER.put("p75", P75Indicator.class);
         REGISTER.put("p50", P50Indicator.class);
+        REGISTER.put("thermodynamic", ThermodynamicIndicator.class);
     }
 
     public static Class<? extends Indicator> find(String functionName) {
diff --git a/oal-parser/src/main/java/org/apache/skywalking/oap/server/core/analysis/indicator/ThermodynamicIndicator.java b/oal-parser/src/main/java/org/apache/skywalking/oap/server/core/analysis/indicator/ThermodynamicIndicator.java
new file mode 100644
index 0000000..d5a0a0f
--- /dev/null
+++ b/oal-parser/src/main/java/org/apache/skywalking/oap/server/core/analysis/indicator/ThermodynamicIndicator.java
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ *
+ */
+
+package org.apache.skywalking.oap.server.core.analysis.indicator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.indicator.annotation.Arg;
+import org.apache.skywalking.oap.server.core.analysis.indicator.annotation.Entrance;
+import org.apache.skywalking.oap.server.core.analysis.indicator.annotation.IndicatorOperator;
+import org.apache.skywalking.oap.server.core.analysis.indicator.annotation.SourceFrom;
+import org.apache.skywalking.oap.server.core.storage.annotation.Column;
+
+/**
+ * Thermodynamic indicator represents the calculator for heat map.
+ *
+ * It groups the given collection of values by the given step and number of steps.
+ *
+ * A heat map (or heatmap) is a graphical representation of data where the individual values contained in a matrix are
+ * represented as colors.
+ *
+ * @author wusheng
+ */
+@IndicatorOperator
+public abstract class ThermodynamicIndicator extends Indicator {
+    protected static final String DETAIL_GROUP = "detail_group";
+    protected static final String STEP = "step";
+    protected static final String NUM_OF_STEPS = "num_of_steps";
+
+    @Getter @Setter @Column(columnName = STEP) private int step = 0;
+    @Getter @Setter @Column(columnName = NUM_OF_STEPS) private int numOfSteps = 0;
+    @Getter @Setter @Column(columnName = DETAIL_GROUP) private List<IntKeyLongValue> detailGroup = new ArrayList<>(30);
+
+    private Map<Integer, IntKeyLongValue> detailIndex;
+
+    /**
+     * @param value
+     * @param step the size of each step. A positive integer.
+     * @param maxNumOfSteps Steps are used to group incoming value.
+     *
+     * Data will be grouped in <br/>
+     *
+     * [0, step), [step, step * 2), ..., [step * (maxNumOfSteps - 1), step * maxNumOfSteps), [step * maxNumOfSteps,
+     * MAX)
+     */
+    @Entrance
+    public final void combine(@SourceFrom int value, @Arg int step, @Arg int maxNumOfSteps) {
+        if (this.step == 0) {
+            this.step = step;
+        }
+        if (this.numOfSteps == 0) {
+            this.numOfSteps = maxNumOfSteps + 1;
+        }
+
+        indexCheckAndInit();
+
+        int index = value / step;
+        if (index > maxNumOfSteps) {
+            index = numOfSteps;
+        }
+        IntKeyLongValue element = detailIndex.get(index);
+        if (element == null) {
+            element = new IntKeyLongValue();
+            element.setKey(index);
+            element.setValue(1);
+            addElement(element);
+        } else {
+            element.addValue(1);
+        }
+    }
+
+    @Override
+    public void combine(Indicator indicator) {
+        ThermodynamicIndicator thermodynamicIndicator = (ThermodynamicIndicator)indicator;
+        this.indexCheckAndInit();
+        thermodynamicIndicator.indexCheckAndInit();
+
+        thermodynamicIndicator.detailIndex.forEach((key, element) -> {
+            IntKeyLongValue existingElement = this.detailIndex.get(key);
+            if (existingElement == null) {
+                existingElement = new IntKeyLongValue();
+                existingElement.setKey(key);
+                existingElement.setValue(element.getValue());
+                addElement(element);
+            } else {
+                existingElement.addValue(element.getValue());
+            }
+        });
+    }
+
+    /**
+     * For Thermodynamic indicator, no single value field. Need to do nothing here.
+     */
+    @Override
+    public final void calculate() {
+
+    }
+
+    private void addElement(IntKeyLongValue element) {
+        detailGroup.add(element);
+        detailIndex.put(element.getKey(), element);
+    }
+
+    private void indexCheckAndInit() {
+        if (detailIndex == null) {
+            detailIndex = new HashMap<>();
+            detailGroup.forEach(element -> detailIndex.put(element.getKey(), element));
+        }
+    }
+}
diff --git a/oal-parser/src/test/resources/oal_test.oal b/oal-parser/src/test/resources/oal_test.oal
index 17634d2..b38b1ae 100644
--- a/oal-parser/src/test/resources/oal_test.oal
+++ b/oal-parser/src/test/resources/oal_test.oal
@@ -22,6 +22,8 @@
 All_p75 = from(All.latency).p75(10);
 All_p50 = from(All.latency).p50(10);
 
+All_heatmap = from(All.latency).thermodynamic(100, 20);
+
 Service_Avg = from(Service.latency).longAvg();
 
 ServiceInstance_RespTime= from(ServiceInstance.latency).longAvg();
diff --git a/oal-syntax/src/main/antlr4/org/apache/skywalking/oal/tool/grammar/OALParser.g4 b/oal-syntax/src/main/antlr4/org/apache/skywalking/oal/tool/grammar/OALParser.g4
index b225d3c..6e1f1f8 100644
--- a/oal-syntax/src/main/antlr4/org/apache/skywalking/oal/tool/grammar/OALParser.g4
+++ b/oal-syntax/src/main/antlr4/org/apache/skywalking/oal/tool/grammar/OALParser.g4
@@ -60,7 +60,7 @@
     ;
 
 aggregateFunction
-    : functionName LR_BRACKET (funcParamExpression | literalExpression)? RR_BRACKET
+    : functionName LR_BRACKET (funcParamExpression | (literalExpression (COMMA literalExpression)?))? RR_BRACKET
     ;
 
 functionName