You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chukwa.apache.org by ey...@apache.org on 2013/09/08 04:20:14 UTC

svn commit: r1520826 - in /incubator/chukwa/trunk: ./ src/main/java/org/apache/hadoop/chukwa/datastore/ src/main/java/org/apache/hadoop/chukwa/hicc/bean/ src/main/java/org/apache/hadoop/chukwa/hicc/rest/ src/main/web/hicc/descriptors/ src/main/web/hicc...

Author: eyang
Date: Sun Sep  8 02:20:14 2013
New Revision: 1520826

URL: http://svn.apache.org/r1520826
Log:
CHUKWA-697. Added generic heatmap REST API and visualization.  (Eric Yang)

Added:
    incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/HeatMapPoint.java
    incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/Heatmap.java
    incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/rest/HeatmapController.java
    incubator/chukwa/trunk/src/main/web/hicc/descriptors/system_heatmap.descriptor
    incubator/chukwa/trunk/src/main/web/hicc/js/heatmap.js   (with props)
    incubator/chukwa/trunk/src/main/web/hicc/jsp/heatmap.jsp
Modified:
    incubator/chukwa/trunk/CHANGES.txt
    incubator/chukwa/trunk/NOTICE.txt
    incubator/chukwa/trunk/pom.xml
    incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/datastore/ChukwaHBaseStore.java

Modified: incubator/chukwa/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/CHANGES.txt?rev=1520826&r1=1520825&r2=1520826&view=diff
==============================================================================
--- incubator/chukwa/trunk/CHANGES.txt (original)
+++ incubator/chukwa/trunk/CHANGES.txt Sun Sep  8 02:20:14 2013
@@ -12,6 +12,8 @@ Release 0.6 - Unreleased
 
   NEW FEATURES
 
+    CHUKWA-697. Added generic heatmap REST API and visualization.  (Eric Yang)
+
     CHUKWA-695. Added ability to export widgets from graph explorer. (Eric Yang)
 
     CHUKWA-581. Added support for custom reducer package name. (Ivy Tang via Eric Yang)

Modified: incubator/chukwa/trunk/NOTICE.txt
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/NOTICE.txt?rev=1520826&r1=1520825&r2=1520826&view=diff
==============================================================================
--- incubator/chukwa/trunk/NOTICE.txt (original)
+++ incubator/chukwa/trunk/NOTICE.txt Sun Sep  8 02:20:14 2013
@@ -60,3 +60,6 @@ Copyright (c) 2006 JUnit.org
 
 This product includes formalize
 Copyright (c) 2012 Nathan Smith, http://formalize.me/
+
+This product includes heatmap.js
+Copyright (c) 2011-2013 Patrick Wied, http://www.patrick-wied.at/static/heatmapjs/

Modified: incubator/chukwa/trunk/pom.xml
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/pom.xml?rev=1520826&r1=1520825&r2=1520826&view=diff
==============================================================================
--- incubator/chukwa/trunk/pom.xml (original)
+++ incubator/chukwa/trunk/pom.xml Sun Sep  8 02:20:14 2013
@@ -388,29 +388,6 @@
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-antrun-plugin</artifactId>
-                <version>1.6</version>
-                <configuration>
-                    <encoding>UTF-8</encoding>
-                </configuration>
-                <executions>
-                        <execution>
-                        <id>chmod-jmx-file</id>
-                        <phase>process-resources</phase>
-                        <configuration>
-                            <tasks name="setup">
-                                <chmod file="target/conf/jmxremote.password" perm="600" />
-                                <chmod file="target/conf/jmxremote.access" perm="600" />
-                            </tasks>
-                        </configuration>
-                        <goals>
-                            <goal>run</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <version>2.3.1</version>
                 <executions>
@@ -597,7 +574,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-antrun-plugin</artifactId>
-                <version>1.4</version>
+                <version>1.6</version>
                 <executions>
                     <execution>
                         <id>setup</id>
@@ -615,6 +592,19 @@
                         </goals>
                     </execution>
                     <execution>
+                        <id>chmod-jmx-file</id>
+                        <phase>process-resources</phase>
+                        <configuration>
+                            <tasks name="setup">
+                                <chmod file="target/conf/jmxremote.password" perm="600" />
+                                <chmod file="target/conf/jmxremote.access" perm="600" />
+                            </tasks>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                    <execution>
                         <id>test-setup</id>
                         <phase>generate-test-resources</phase>
                         <configuration>

Modified: incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/datastore/ChukwaHBaseStore.java
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/datastore/ChukwaHBaseStore.java?rev=1520826&r1=1520825&r2=1520826&view=diff
==============================================================================
--- incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/datastore/ChukwaHBaseStore.java (original)
+++ incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/datastore/ChukwaHBaseStore.java Sun Sep  8 02:20:14 2013
@@ -17,15 +17,20 @@
  */
 package org.apache.hadoop.chukwa.datastore;
 
+import java.io.IOException;
 import java.util.Calendar;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.apache.hadoop.chukwa.hicc.bean.HeatMapPoint;
+import org.apache.hadoop.chukwa.hicc.bean.Heatmap;
 import org.apache.hadoop.chukwa.hicc.bean.Series;
 import org.apache.hadoop.chukwa.util.ExceptionUtil;
 
@@ -39,6 +44,7 @@ import org.apache.hadoop.hbase.client.HT
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.ResultScanner;
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.filter.ColumnPrefixFilter;
 import org.apache.hadoop.hbase.filter.RowFilter;
 import org.apache.hadoop.hbase.filter.RegexStringComparator;
 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
@@ -251,4 +257,81 @@ public class ChukwaHBaseStore {
     }
     return clusters;
   }
+  
+  public static Heatmap getHeatmap(String tableName, String family, String column, 
+		  long startTime, long endTime, double max, double scale, int height) {
+	final long MINUTE = TimeUnit.MINUTES.toMillis(1);
+	Heatmap heatmap = new Heatmap();
+    HTableInterface table = pool.getTable(tableName);
+    try {
+        Scan scan = new Scan();
+        ColumnPrefixFilter cpf = new ColumnPrefixFilter(column.getBytes());
+        scan.addFamily(family.getBytes());
+        scan.setFilter(cpf);
+    	scan.setTimeRange(startTime, endTime);
+    	scan.setBatch(10000);
+        ResultScanner results = table.getScanner(scan);
+	    Iterator<Result> it = results.iterator();
+		int index = 0;
+		// Series display in y axis
+		int y = 0;
+		HashMap<String, Integer> keyMap = new HashMap<String, Integer>();
+	    while(it.hasNext()) {
+		  Result result = it.next();
+		  List<KeyValue> kvList = result.list();
+	      for(KeyValue kv : kvList) {
+			String key = parseRowKey(result.getRow());
+			StringBuilder tmp = new StringBuilder();
+			tmp.append(key);
+			tmp.append(":");
+			tmp.append(new String(kv.getQualifier()));
+			String seriesName = tmp.toString();
+			long time = parseTime(result.getRow());
+			// Time display in x axis
+			int x = (int) ((time - startTime) / MINUTE);
+			if(keyMap.containsKey(seriesName)) {
+			  y = keyMap.get(seriesName);
+		    } else {
+			  keyMap.put(seriesName, new Integer(index));
+		      y = index;
+		      index++;
+			}
+			double v = Double.parseDouble(new String(kv.getValue()));
+			heatmap.put(x, y, v);
+			if(v > max) {
+				max = v;
+			}
+	      }
+	    }
+	    results.close();
+	    table.close();
+	    int radius = height / index;
+	    // Usually scale max from 0 to 100 for visualization
+	    heatmap.putMax(scale);
+	    for(HeatMapPoint point : heatmap.getHeatmap()) {
+	      double round = point.count / max * scale;
+	      round = Math.round(round * 100.0) / 100.0;
+	      point.put(point.x, point.y * radius, round);
+	    }
+	    heatmap.putRadius(radius);
+	    heatmap.putSeries(index);
+	} catch (IOException e) {
+	    log.error(ExceptionUtil.getStackTrace(e));
+	}
+	return heatmap;
+  }
+  
+  private static String parseRowKey(byte[] row) {
+	  String key = new String(row);
+	  String[] parts = key.split("-", 2);
+	  return parts[1];
+  }
+
+  private static long parseTime(byte[] row) {
+	  String key = new String(row);
+	  String[] parts = key.split("-", 2);
+	  long time = Long.parseLong(parts[0]);
+	  return time;
+  }
+
 }

Added: incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/HeatMapPoint.java
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/HeatMapPoint.java?rev=1520826&view=auto
==============================================================================
--- incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/HeatMapPoint.java (added)
+++ incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/HeatMapPoint.java Sun Sep  8 02:20:14 2013
@@ -0,0 +1,31 @@
+package org.apache.hadoop.chukwa.hicc.bean;
+
+import javax.xml.bind.annotation.XmlElement;
+
+public class HeatMapPoint {
+  @XmlElement
+  public int x;
+  @XmlElement
+  public int y;
+  @XmlElement
+  public double count;
+
+  HeatMapPoint() {
+  }
+
+  HeatMapPoint(int x, int y, double count) {
+    this.x = x;
+	this.y = y;
+	this.count = count;
+  }
+
+  public HeatMapPoint get() {
+	return this;
+  }
+
+  public void put(int x, int y, double count) {
+	this.x = x;
+	this.y = y;
+	this.count = count;
+  }
+}

Added: incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/Heatmap.java
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/Heatmap.java?rev=1520826&view=auto
==============================================================================
--- incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/Heatmap.java (added)
+++ incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/bean/Heatmap.java Sun Sep  8 02:20:14 2013
@@ -0,0 +1,60 @@
+package org.apache.hadoop.chukwa.hicc.bean;
+
+import java.util.ArrayList;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(propOrder={})
+public class Heatmap {
+  @XmlElement
+  private ArrayList<HeatMapPoint> data;
+  @XmlElement
+  private double max = 1.0;
+  @XmlElement
+  private int radius;
+  @XmlElement
+  private int series;
+  
+  public Heatmap() {
+	  this.data = new ArrayList<HeatMapPoint>();
+  }
+  
+  public void put(int x, int y, double v) {
+	  HeatMapPoint point = new HeatMapPoint(x, y, v);
+	  data.add(point);
+  }
+  
+  public ArrayList<HeatMapPoint> getHeatmap() {
+	  return data;
+  }
+  
+  public double getMax() {
+	  return max;
+  }
+  
+  public void putMax(double max) {
+	  this.max = max;
+  }
+
+  public int getRadius() {
+	  return radius;
+  }
+  
+  public void putRadius(int radius) {
+	  this.radius = radius;
+  }
+  
+  public int getSeries() {
+	  return series;
+  }
+  
+  public void putSeries(int series) {
+	  this.series = series;
+  }
+}

Added: incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/rest/HeatmapController.java
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/rest/HeatmapController.java?rev=1520826&view=auto
==============================================================================
--- incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/rest/HeatmapController.java (added)
+++ incubator/chukwa/trunk/src/main/java/org/apache/hadoop/chukwa/hicc/rest/HeatmapController.java Sun Sep  8 02:20:14 2013
@@ -0,0 +1,76 @@
+/*
+ * 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.hadoop.chukwa.hicc.rest;
+
+import java.text.SimpleDateFormat;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.hadoop.chukwa.datastore.ChukwaHBaseStore;
+import org.apache.hadoop.chukwa.hicc.TimeHandler;
+import org.apache.hadoop.chukwa.hicc.bean.Heatmap;
+import org.apache.hadoop.chukwa.util.ExceptionUtil;
+import org.apache.log4j.Logger;
+
+@Path("/heatmap")
+public class HeatmapController {
+  static Logger log = Logger.getLogger(HeatmapController.class);
+
+  @GET
+  @Path("{table}/{family}/{column}")
+  @Produces(MediaType.APPLICATION_JSON)
+  public Heatmap getHeatmap(@Context HttpServletRequest request, 
+		  @PathParam("table") String table, 
+		  @PathParam("family") String family, 
+		  @PathParam("column") String column, 
+		  @QueryParam("start") String start, 
+		  @QueryParam("end") String end, 
+		  @QueryParam("max") @DefaultValue("1.0") double max,
+		  @QueryParam("scale") @DefaultValue("100") double scale,
+		  @QueryParam("height") @DefaultValue("400") int height) {
+	  SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+	  Heatmap heatmap = null;
+	  long startTime = 0;
+	    long endTime = 0;
+	    TimeHandler time = new TimeHandler(request);
+	    try {
+	      if(start!=null) {
+	        startTime = sdf.parse(start).getTime();
+	      } else {
+	        startTime = time.getStartTime();
+	      }
+	      if(end!=null) {
+	        endTime = sdf.parse(end).getTime();
+	      } else {
+	        endTime = time.getEndTime();
+	      }
+	      heatmap = ChukwaHBaseStore.getHeatmap(table, family, column, startTime, endTime, max, scale, height);
+	    }catch(Throwable e) {
+		    log.error(ExceptionUtil.getStackTrace(e));
+	    }
+	  return heatmap;
+  }
+}

Added: incubator/chukwa/trunk/src/main/web/hicc/descriptors/system_heatmap.descriptor
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/src/main/web/hicc/descriptors/system_heatmap.descriptor?rev=1520826&view=auto
==============================================================================
--- incubator/chukwa/trunk/src/main/web/hicc/descriptors/system_heatmap.descriptor (added)
+++ incubator/chukwa/trunk/src/main/web/hicc/descriptors/system_heatmap.descriptor Sun Sep  8 02:20:14 2013
@@ -0,0 +1,38 @@
+{
+"id":"system_heatmap",
+"title":"System Heatmap",
+"version":"0.1",
+"categories":"System Metrics,Server",
+"url":"iframe/jsp/heatmap.jsp",
+"description":"Display system related stats",
+"screendump":"\/images\/server_load.gif",
+"refresh":"15",
+"parameters":[
+{"name":"title","type":"string","value":"CPU Utilization","edit":"1","label":"Title"},
+{"name":"period","type":"custom","control":"period_control","value":"","label":"Period"},
+{"name":"url","type":"select","value":"/hicc/v1/heatmap/SystemMetrics/cpu/combined.","label":"Metric","options":[
+{"label":"CPU Utilization Distribution","value":"/hicc/v1/heatmap/SystemMetrics/cpu/combined.?max=1"},
+{"label":"Memory Utilization Distribution","value":"/hicc/v1/heatmap/SystemMetrics/memory/ActualUsed?max=16000000000"},
+{"label":"Disk Read Distribution","value":"/hicc/v1/heatmap/SystemMetrics/disk/ReadBytes.?max=125000000"},
+{"label":"Disk Write Distribution","value":"/hicc/v1/heatmap/SystemMetrics/disk/WriteBytes.?max=125000000"},
+{"label":"Network Transfer Distribution","value":"/hicc/v1/heatmap/SystemMetrics/network/TxBytes.?max=125000000"}
+{"label":"Network Receive Distribution","value":"/hicc/v1/heatmap/SystemMetrics/network/RxBytes.?max=125000000"}
+]},
+{"name":"width","type":"select","value":"300","label":"Width","options":[
+{"label":"300","value":"300"},
+{"label":"400","value":"400"},
+{"label":"500","value":"500"},
+{"label":"600","value":"600"},
+{"label":"800","value":"800"},
+{"label":"1000","value":"1000"},
+{"label":"1200","value":"1200"}
+]},
+{"name":"height","type":"select","value":"200","label":"Height","options":[
+{"label":"200","value":"200"},
+{"label":"400","value":"400"},
+{"label":"600","value":"600"},
+{"label":"800","value":"800"},
+{"label":"1000","value":"1000"}
+]}
+]
+}

Added: incubator/chukwa/trunk/src/main/web/hicc/js/heatmap.js
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/src/main/web/hicc/js/heatmap.js?rev=1520826&view=auto
==============================================================================
--- incubator/chukwa/trunk/src/main/web/hicc/js/heatmap.js (added)
+++ incubator/chukwa/trunk/src/main/web/hicc/js/heatmap.js Sun Sep  8 02:20:14 2013
@@ -0,0 +1,653 @@
+/*
+ * heatmap.js 1.0 -    JavaScript Heatmap Library
+ *
+ * Copyright (c) 2011, Patrick Wied (http://www.patrick-wied.at)
+ * Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and the Beerware (http://en.wikipedia.org/wiki/Beerware) license.
+ */
+
+(function(w){
+    // the heatmapFactory creates heatmap instances
+    var heatmapFactory = (function(){
+
+    // store object constructor
+    // a heatmap contains a store
+    // the store has to know about the heatmap in order to trigger heatmap updates when datapoints get added
+    var store = function store(hmap){
+
+        var _ = {
+            // data is a two dimensional array
+            // a datapoint gets saved as data[point-x-value][point-y-value]
+            // the value at [point-x-value][point-y-value] is the occurrence of the datapoint
+            data: [],
+            // tight coupling of the heatmap object
+            heatmap: hmap
+        };
+        // the max occurrence - the heatmaps radial gradient alpha transition is based on it
+        this.max = 1;
+
+        this.get = function(key){
+            return _[key];
+        };
+        this.set = function(key, value){
+            _[key] = value;
+        };
+    }
+
+    store.prototype = {
+        // function for adding datapoints to the store
+        // datapoints are usually defined by x and y but could also contain a third parameter which represents the occurrence
+        addDataPoint: function(x, y){
+            if(x < 0 || y < 0)
+                return;
+
+            var me = this,
+                heatmap = me.get("heatmap"),
+                data = me.get("data");
+
+            if(!data[x])
+                data[x] = [];
+
+            if(!data[x][y])
+                data[x][y] = 0;
+
+            // if count parameter is set increment by count otherwise by 1
+            data[x][y]+=(arguments.length<3)?1:arguments[2];
+            
+            me.set("data", data);
+            // do we have a new maximum?
+            if(me.max < data[x][y]){
+                // max changed, we need to redraw all existing(lower) datapoints
+                heatmap.get("actx").clearRect(0,0,heatmap.get("width"),heatmap.get("height"));
+                me.setDataSet({ max: data[x][y], data: data }, true);
+                return;
+            }
+            heatmap.drawAlpha(x, y, data[x][y], true);
+        },
+        setDataSet: function(obj, internal){
+            var me = this,
+                heatmap = me.get("heatmap"),
+                data = [],
+                d = obj.data,
+                dlen = d.length;
+            // clear the heatmap before the data set gets drawn
+            heatmap.clear();
+            this.max = obj.max;
+            // if a legend is set, update it
+            heatmap.get("legend") && heatmap.get("legend").update(obj.max);
+            
+            if(internal != null && internal){
+                for(var one in d){
+                    // jump over undefined indexes
+                    if(one === undefined)
+                        continue;
+                    for(var two in d[one]){
+                        if(two === undefined)
+                            continue;
+                        // if both indexes are defined, push the values into the array
+                        heatmap.drawAlpha(one, two, d[one][two], false);   
+                    }
+                }
+            }else{
+                while(dlen--){
+                    var point = d[dlen];
+                    heatmap.drawAlpha(point.x, point.y, point.count, false);
+                    if(!data[point.x])
+                        data[point.x] = [];
+
+                    if(!data[point.x][point.y])
+                        data[point.x][point.y] = 0;
+
+                    data[point.x][point.y] = point.count;
+                }
+            }
+            heatmap.colorize();
+            this.set("data", d);
+        },
+        exportDataSet: function(){
+            var me = this,
+                data = me.get("data"),
+                exportData = [];
+
+            for(var one in data){
+                // jump over undefined indexes
+                if(one === undefined)
+                    continue;
+                for(var two in data[one]){
+                    if(two === undefined)
+                        continue;
+                    // if both indexes are defined, push the values into the array
+                    exportData.push({x: parseInt(one, 10), y: parseInt(two, 10), count: data[one][two]});
+                }
+            }
+
+            return { max: me.max, data: exportData };
+        },
+        generateRandomDataSet: function(points){
+            var heatmap = this.get("heatmap"),
+            w = heatmap.get("width"),
+            h = heatmap.get("height");
+            var randomset = {},
+            max = Math.floor(Math.random()*1000+1);
+            randomset.max = max;
+            var data = [];
+            while(points--){
+                data.push({x: Math.floor(Math.random()*w+1), y: Math.floor(Math.random()*h+1), count: Math.floor(Math.random()*max+1)});
+            }
+            randomset.data = data;
+            this.setDataSet(randomset);
+        }
+    };
+
+    var legend = function legend(config){
+        this.config = config;
+
+        var _ = {
+            element: null,
+            labelsEl: null,
+            gradientCfg: null,
+            ctx: null
+        };
+        this.get = function(key){
+            return _[key];
+        };
+        this.set = function(key, value){
+            _[key] = value;
+        };
+        this.init();
+    };
+    legend.prototype = {
+        init: function(){
+            var me = this,
+                config = me.config,
+                title = config.title || "Legend",
+                position = config.position,
+                offset = config.offset || 10,
+                gconfig = config.gradient,
+                labelsEl = document.createElement("ul"),
+                labelsHtml = "",
+                grad, element, gradient, positionCss = "";
+ 
+            me.processGradientObject();
+            
+            // Positioning
+
+            // top or bottom
+            if(position.indexOf('t') > -1){
+                positionCss += 'top:'+offset+'px;';
+            }else{
+                positionCss += 'bottom:'+offset+'px;';
+            }
+
+            // left or right
+            if(position.indexOf('l') > -1){
+                positionCss += 'left:'+offset+'px;';
+            }else{
+                positionCss += 'right:'+offset+'px;';
+            }
+
+            element = document.createElement("div");
+            element.style.cssText = "border-radius:5px;position:absolute;"+positionCss+"font-family:Helvetica; width:256px;z-index:10000000000; background:rgba(255,255,255,1);padding:10px;border:1px solid black;margin:0;";
+            element.innerHTML = "<h3 style='padding:0;margin:0;text-align:center;font-size:16px;'>"+title+"</h3>";
+            // create gradient in canvas
+            labelsEl.style.cssText = "position:relative;font-size:12px;display:block;list-style:none;list-style-type:none;margin:0;height:15px;";
+            
+
+            // create gradient element
+            gradient = document.createElement("div");
+            gradient.style.cssText = ["position:relative;display:block;width:256px;height:15px;border-bottom:1px solid black; background-image:url(",me.createGradientImage(),");"].join("");
+
+            element.appendChild(labelsEl);
+            element.appendChild(gradient);
+            
+            me.set("element", element);
+            me.set("labelsEl", labelsEl);
+
+            me.update(1);
+        },
+        processGradientObject: function(){
+            // create array and sort it
+            var me = this,
+                gradientConfig = this.config.gradient,
+                gradientArr = [];
+
+            for(var key in gradientConfig){
+                if(gradientConfig.hasOwnProperty(key)){
+                    gradientArr.push({ stop: key, value: gradientConfig[key] });
+                }
+            }
+            gradientArr.sort(function(a, b){
+                return (a.stop - b.stop);
+            });
+            gradientArr.unshift({ stop: 0, value: 'rgba(0,0,0,0)' });
+
+            me.set("gradientArr", gradientArr);
+        },
+        createGradientImage: function(){
+            var me = this,
+                gradArr = me.get("gradientArr"),
+                length = gradArr.length,
+                canvas = document.createElement("canvas"),
+                ctx = canvas.getContext("2d"),
+                grad;
+            // the gradient in the legend including the ticks will be 256x15px
+            canvas.width = "256";
+            canvas.height = "15";
+
+            grad = ctx.createLinearGradient(0,5,256,10);
+
+            for(var i = 0; i < length; i++){
+                grad.addColorStop(1/(length-1) * i, gradArr[i].value);
+            }
+
+            ctx.fillStyle = grad;
+            ctx.fillRect(0,5,256,10);
+            ctx.strokeStyle = "black";
+            ctx.beginPath();
+ 
+            for(var i = 0; i < length; i++){
+                ctx.moveTo(((1/(length-1)*i*256) >> 0)+.5, 0);
+                ctx.lineTo(((1/(length-1)*i*256) >> 0)+.5, (i==0)?15:5);
+            }
+            ctx.moveTo(255.5, 0);
+            ctx.lineTo(255.5, 15);
+            ctx.moveTo(255.5, 4.5);
+            ctx.lineTo(0, 4.5);
+            
+            ctx.stroke();
+
+            // we re-use the context for measuring the legends label widths
+            me.set("ctx", ctx);
+
+            return canvas.toDataURL();
+        },
+        getElement: function(){
+            return this.get("element");
+        },
+        update: function(max){
+            var me = this,
+                gradient = me.get("gradientArr"),
+                ctx = me.get("ctx"),
+                labels = me.get("labelsEl"),
+                labelText, labelsHtml = "", offset;
+
+            for(var i = 0; i < gradient.length; i++){
+
+                labelText = max*gradient[i].stop >> 0;
+                offset = (ctx.measureText(labelText).width/2) >> 0;
+
+                if(i == 0){
+                    offset = 0;
+                }
+                if(i == gradient.length-1){
+                    offset *= 2;
+                }
+                labelsHtml += '<li style="position:absolute;left:'+(((((1/(gradient.length-1)*i*256) || 0)) >> 0)-offset+.5)+'px">'+labelText+'</li>';
+            }       
+            labels.innerHTML = labelsHtml;
+        }
+    };
+
+    // heatmap object constructor
+    var heatmap = function heatmap(config){
+        // private variables
+        var _ = {
+            radius : 40,
+            element : {},
+            canvas : {},
+            acanvas: {},
+            ctx : {},
+            actx : {},
+            legend: null,
+            visible : true,
+            width : 0,
+            height : 0,
+            max : false,
+            gradient : false,
+            opacity: 180,
+            premultiplyAlpha: false,
+            bounds: {
+                l: 1000,
+                r: 0,
+                t: 1000,
+                b: 0
+            },
+            debug: false
+        };
+        // heatmap store containing the datapoints and information about the maximum
+        // accessible via instance.store
+        this.store = new store(this);
+
+        this.get = function(key){
+            return _[key];
+        };
+        this.set = function(key, value){
+            _[key] = value;
+        };
+        // configure the heatmap when an instance gets created
+        this.configure(config);
+        // and initialize it
+        this.init();
+    };
+
+    // public functions
+    heatmap.prototype = {
+        configure: function(config){
+                var me = this,
+                    rout, rin;
+
+                me.set("radius", config["radius"] || 40);
+                me.set("element", (config.element instanceof Object)?config.element:document.getElementById(config.element));
+                me.set("visible", (config.visible != null)?config.visible:true);
+                me.set("max", config.max || false);
+                me.set("gradient", config.gradient || { 0.45: "rgb(0,0,255)", 0.55: "rgb(0,255,255)", 0.65: "rgb(0,255,0)", 0.95: "yellow", 1.0: "rgb(255,0,0)"});    // default is the common blue to red gradient
+                me.set("opacity", parseInt(255/(100/config.opacity), 10) || 180);
+                me.set("width", config.width || 0);
+                me.set("height", config.height || 0);
+                me.set("debug", config.debug);
+
+                if(config.legend){
+                    var legendCfg = config.legend;
+                    legendCfg.gradient = me.get("gradient");
+                    me.set("legend", new legend(legendCfg));
+                }
+                
+        },
+        resize: function () {
+                var me = this,
+                    element = me.get("element"),
+                    canvas = me.get("canvas"),
+                    acanvas = me.get("acanvas");
+                canvas.width = acanvas.width = me.get("width") || element.style.width.replace(/px/, "") || me.getWidth(element);
+                this.set("width", canvas.width);
+                canvas.height = acanvas.height = me.get("height") || element.style.height.replace(/px/, "") || me.getHeight(element);
+                this.set("height", canvas.height);
+        },
+
+        init: function(){
+                var me = this,
+                    canvas = document.createElement("canvas"),
+                    acanvas = document.createElement("canvas"),
+                    ctx = canvas.getContext("2d"),
+                    actx = acanvas.getContext("2d"),
+                    element = me.get("element");
+
+                
+                me.initColorPalette();
+
+                me.set("canvas", canvas);
+                me.set("ctx", ctx);
+                me.set("acanvas", acanvas);
+                me.set("actx", actx);
+
+                me.resize();
+                canvas.style.cssText = acanvas.style.cssText = "position:absolute;top:0;left:0;z-index:10000000;";
+                
+                if(!me.get("visible"))
+                    canvas.style.display = "none";
+
+                element.appendChild(canvas);
+                if(me.get("legend")){
+                    element.appendChild(me.get("legend").getElement());
+                }
+                
+                // debugging purposes only
+                if(me.get("debug"))
+                    document.body.appendChild(acanvas);
+                
+                actx.shadowOffsetX = 15000; 
+                actx.shadowOffsetY = 15000; 
+                actx.shadowBlur = 15; 
+        },
+        initColorPalette: function(){
+
+            var me = this,
+                canvas = document.createElement("canvas"),
+                gradient = me.get("gradient"),
+                ctx, grad, testData;
+
+            canvas.width = "1";
+            canvas.height = "256";
+            ctx = canvas.getContext("2d");
+            grad = ctx.createLinearGradient(0,0,1,256);
+
+            // Test how the browser renders alpha by setting a partially transparent pixel
+            // and reading the result.  A good browser will return a value reasonably close
+            // to what was set.  Some browsers (e.g. on Android) will return a ridiculously wrong value.
+            testData = ctx.getImageData(0,0,1,1);
+            testData.data[0] = testData.data[3] = 64; // 25% red & alpha
+            testData.data[1] = testData.data[2] = 0; // 0% blue & green
+            ctx.putImageData(testData, 0, 0);
+            testData = ctx.getImageData(0,0,1,1);
+            me.set("premultiplyAlpha", (testData.data[0] < 60 || testData.data[0] > 70));
+            
+            for(var x in gradient){
+                grad.addColorStop(x, gradient[x]);
+            }
+
+            ctx.fillStyle = grad;
+            ctx.fillRect(0,0,1,256);
+
+            me.set("gradient", ctx.getImageData(0,0,1,256).data);
+        },
+        getWidth: function(element){
+            var width = element.offsetWidth;
+            if(element.style.paddingLeft){
+                width+=element.style.paddingLeft;
+            }
+            if(element.style.paddingRight){
+                width+=element.style.paddingRight;
+            }
+
+            return width;
+        },
+        getHeight: function(element){
+            var height = element.offsetHeight;
+            if(element.style.paddingTop){
+                height+=element.style.paddingTop;
+            }
+            if(element.style.paddingBottom){
+                height+=element.style.paddingBottom;
+            }
+
+            return height;
+        },
+        colorize: function(x, y){
+                // get the private variables
+                var me = this,
+                    width = me.get("width"),
+                    radius = me.get("radius"),
+                    height = me.get("height"),
+                    actx = me.get("actx"),
+                    ctx = me.get("ctx"),
+                    x2 = radius * 3,
+                    premultiplyAlpha = me.get("premultiplyAlpha"),
+                    palette = me.get("gradient"),
+                    opacity = me.get("opacity"),
+                    bounds = me.get("bounds"),
+                    left, top, bottom, right, 
+                    image, imageData, length, alpha, offset, finalAlpha;
+                
+                if(x != null && y != null){
+                    if(x+x2>width){
+                        x=width-x2;
+                    }
+                    if(x<0){
+                        x=0;
+                    }
+                    if(y<0){
+                        y=0;
+                    }
+                    if(y+x2>height){
+                        y=height-x2;
+                    }
+                    left = x;
+                    top = y;
+                    right = x + x2;
+                    bottom = y + x2;
+
+                }else{
+                    if(bounds['l'] < 0){
+                        left = 0;
+                    }else{
+                        left = bounds['l'];
+                    }
+                    if(bounds['r'] > width){
+                        right = width;
+                    }else{
+                        right = bounds['r'];
+                    }
+                    if(bounds['t'] < 0){
+                        top = 0;
+                    }else{
+                        top = bounds['t'];
+                    }
+                    if(bounds['b'] > height){
+                        bottom = height;
+                    }else{
+                        bottom = bounds['b'];
+                    }    
+                }
+
+                image = actx.getImageData(left, top, right-left, bottom-top);
+                imageData = image.data;
+                length = imageData.length;
+                // loop thru the area
+                for(var i=3; i < length; i+=4){
+
+                    // [0] -> r, [1] -> g, [2] -> b, [3] -> alpha
+                    alpha = imageData[i],
+                    offset = alpha*4;
+
+                    if(!offset)
+                        continue;
+
+                    // we ve started with i=3
+                    // set the new r, g and b values
+                    finalAlpha = (alpha < opacity)?alpha:opacity;
+                    imageData[i-3]=palette[offset];
+                    imageData[i-2]=palette[offset+1];
+                    imageData[i-1]=palette[offset+2];
+                    
+                    if (premultiplyAlpha) {
+                    	// To fix browsers that premultiply incorrectly, we'll pass in a value scaled
+                    	// appropriately so when the multiplication happens the correct value will result.
+                    	imageData[i-3] /= 255/finalAlpha;
+                    	imageData[i-2] /= 255/finalAlpha;
+                    	imageData[i-1] /= 255/finalAlpha;
+                    }
+                    
+                    // we want the heatmap to have a gradient from transparent to the colors
+                    // as long as alpha is lower than the defined opacity (maximum), we'll use the alpha value
+                    imageData[i] = finalAlpha;
+                }
+                // the rgb data manipulation didn't affect the ImageData object(defined on the top)
+                // after the manipulation process we have to set the manipulated data to the ImageData object
+                image.data = imageData;
+                ctx.putImageData(image, left, top);
+        },
+        drawAlpha: function(x, y, count, colorize){
+                // storing the variables because they will be often used
+                var me = this,
+                    radius = me.get("radius"),
+                    ctx = me.get("actx"),
+                    max = me.get("max"),
+                    bounds = me.get("bounds"),
+                    xb = x - (1.5 * radius) >> 0, yb = y - (1.5 * radius) >> 0,
+                    xc = x + (1.5 * radius) >> 0, yc = y + (1.5 * radius) >> 0;
+
+                ctx.shadowColor = ('rgba(0,0,0,'+((count)?(count/me.store.max):'0.1')+')');
+
+                ctx.shadowOffsetX = 15000; 
+                ctx.shadowOffsetY = 15000; 
+                ctx.shadowBlur = 15; 
+
+                ctx.beginPath();
+                ctx.arc(x - 15000, y - 15000, radius, 0, Math.PI * 2, true);
+                ctx.closePath();
+                ctx.fill();
+                if(colorize){
+                    // finally colorize the area
+                    me.colorize(xb,yb);
+                }else{
+                    // or update the boundaries for the area that then should be colorized
+                    if(xb < bounds["l"]){
+                        bounds["l"] = xb;
+                    }
+                    if(yb < bounds["t"]){
+                        bounds["t"] = yb;
+                    }
+                    if(xc > bounds['r']){
+                        bounds['r'] = xc;
+                    }
+                    if(yc > bounds['b']){
+                        bounds['b'] = yc;
+                    }
+                }
+        },
+        toggleDisplay: function(){
+                var me = this,
+                    visible = me.get("visible"),
+                canvas = me.get("canvas");
+
+                if(!visible)
+                    canvas.style.display = "block";
+                else
+                    canvas.style.display = "none";
+
+                me.set("visible", !visible);
+        },
+        // dataURL export
+        getImageData: function(){
+                return this.get("canvas").toDataURL();
+        },
+        clear: function(){
+            var me = this,
+                w = me.get("width"),
+                h = me.get("height");
+
+            me.store.set("data",[]);
+            // @TODO: reset stores max to 1
+            //me.store.max = 1;
+            me.get("ctx").clearRect(0,0,w,h);
+            me.get("actx").clearRect(0,0,w,h);
+        },
+        cleanup: function(){
+            var me = this;
+            me.get("element").removeChild(me.get("canvas"));
+        }
+    };
+
+    return {
+            create: function(config){
+                return new heatmap(config);
+            }, 
+            util: {
+                mousePosition: function(ev){
+                    // this doesn't work right
+                    // rather use
+                    /*
+                        // this = element to observe
+                        var x = ev.pageX - this.offsetLeft;
+                        var y = ev.pageY - this.offsetTop;
+
+                    */
+                    var x, y;
+
+                    if (ev.layerX) { // Firefox
+                        x = ev.layerX;
+                        y = ev.layerY;
+                    } else if (ev.offsetX) { // Opera
+                        x = ev.offsetX;
+                        y = ev.offsetY;
+                    }
+                    if(typeof(x)=='undefined')
+                        return;
+
+                    return [x,y];
+                }
+            }
+        };
+    })();
+    w.h337 = w.heatmapFactory = heatmapFactory;
+})(window);

Propchange: incubator/chukwa/trunk/src/main/web/hicc/js/heatmap.js
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/chukwa/trunk/src/main/web/hicc/jsp/heatmap.jsp
URL: http://svn.apache.org/viewvc/incubator/chukwa/trunk/src/main/web/hicc/jsp/heatmap.jsp?rev=1520826&view=auto
==============================================================================
--- incubator/chukwa/trunk/src/main/web/hicc/jsp/heatmap.jsp (added)
+++ incubator/chukwa/trunk/src/main/web/hicc/jsp/heatmap.jsp Sun Sep  8 02:20:14 2013
@@ -0,0 +1,128 @@
+<%
+/*
+ * 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.
+ */
+%>
+<%@ page import = "java.util.Hashtable, java.util.Enumeration, java.util.Calendar, java.util.Date, java.text.SimpleDateFormat, org.apache.hadoop.chukwa.hicc.TimeHandler, java.text.NumberFormat, org.apache.hadoop.chukwa.util.XssFilter" %>
+<%
+   XssFilter xf = new XssFilter(request);
+   response.setContentType("text/html; chartset=UTF-8//IGNORE");
+   response.setHeader("boxId", xf.getParameter("boxId"));
+
+   String width = "600";
+   if(xf.getParameter("width")!=null) {
+     width=xf.getParameter("width");
+   }
+
+   String height = "400";
+   if(xf.getParameter("height")!=null) {
+     height=xf.getParameter("height");
+   }
+
+   String yLabel = "device";
+   if(xf.getParameter("yLabel")!=null) {
+     yLabel=xf.getParameter("yLabel");
+   }
+
+   String url = "/hicc/v1/heatmap/SystemMetrics/cpu/combined.?max=100";
+   if(xf.getParameter("url")!=null) {
+     url=xf.getParameter("url");
+   }
+%>
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <style>
+
+      #heatmapArea {
+        display: block;
+        position:absolute;
+        float:left;
+        width: <%= width %>px;
+        height: <%= height %>px;
+        top:0;
+        left: 50px;
+      }
+
+      #yaxis {
+        text-align: center;
+        width: 50px;
+        height: <%= height %>px;
+        line-height: 400px;
+      }
+
+      p {
+        border:0px solid red;
+        writing-mode:lr-tb;
+        -webkit-transform:rotate(270deg);
+        -moz-transform:rotate(270deg);
+        -o-transform: rotate(270deg);
+        white-space:nowrap;
+        bottom:0;
+      }
+
+      #xaxis {
+        width: <%= width %>px;
+        position: absolute;
+        left: 0px;
+        bottom: 10px;
+        height: 20px;
+        text-align: center;
+        display: block;
+      }
+
+      body {
+        color:#333;
+        font-family: Oswald, Helvetica, Arial;
+        font-weight:normal;
+      }
+
+    </style>
+    <link href="/hicc/css/default.css" rel="stylesheet" type="text/css">
+  </head>
+  <body>
+    <div id="yaxis">
+      <p id="yLabel"></p>
+    </div>
+    <div id="heatmapArea"></div>
+    <div id="xaxis">Time</div>
+    <script src="/hicc/js/jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
+    <script type="text/javascript" src="/hicc/js/heatmap.js"></script>
+    <script type="text/javascript">
+      window.onload = function() {
+        $.ajax({ 
+          url: "<%= url %>", 
+          dataType: "json", 
+          success: function(data) {
+            $('#yLabel').html(data.series + " <%= yLabel %>(s)");
+            var config = {
+              element: document.getElementById("heatmapArea"),
+              radius: data.radius/2,
+              opacity: 50,
+              legend: {
+                position: 'br',
+                title: '<%= xf.getParameter("title") %> Distribution'
+              }
+            };
+            var heatmap = h337.create(config);
+            heatmap.store.setDataSet(data);
+          }
+        });
+      };
+    </script>
+  </body>
+</html>