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>