This project has retired. For details please refer to its Attic page.
Heatmap xref
View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18   
19  package org.apache.hadoop.chukwa.analysis.salsa.visualization;
20  
21  
22  import prefuse.data.*;
23  import prefuse.action.*;
24  import prefuse.action.layout.*;
25  import prefuse.action.assignment.*;
26  import prefuse.visual.*;
27  import prefuse.render.*;
28  import prefuse.util.*;
29  import prefuse.*;
30  
31  import org.apache.hadoop.chukwa.hicc.OfflineTimeHandler;
32  import org.apache.hadoop.chukwa.hicc.TimeHandler;
33  import org.apache.hadoop.chukwa.util.DatabaseWriter;
34  import org.apache.hadoop.chukwa.database.Macro;
35  import org.apache.hadoop.chukwa.util.XssFilter;
36  
37  import javax.servlet.http.*;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  import java.sql.*;
43  import java.util.*;
44  
45  import java.awt.Font;
46  import java.awt.geom.Rectangle2D;
47  import java.awt.Color;
48  
49  /**
50   * Static image rendering for heatmap visualization of spatial HDFS 
51   * activity patterns for scalable rendering on front-end (web-browser)
52   * Handles database data retrieval, transforming data to form for 
53   * visualization elements, and initializing and calling visualization
54   * elements 
55   */
56  public class Heatmap {
57  
58    /**
59     * Internal representation of all data needed to render heatmap;
60     * data-handling code populates this data structure
61     */
62    protected static class HeatmapData {
63      public Table agg_tab;
64      public long [][] stats;
65      public long min;
66      public long max;
67      public int num_hosts;
68      public String [] hostnames;
69      public HeatmapData() {
70      }
71    }
72  
73    private static Log log = LogFactory.getLog(Heatmap.class);
74  
75    static final String START_FIELD_NAME = "start_time_num";
76    static final String END_FIELD_NAME = "finish_time_num";
77  
78    int BOXWIDTH = 250;
79    int SIZE_X = 1600, SIZE_Y=1600;
80    final int [] BORDER = {200,150,150,150};
81    final int LEGEND_X_OFFSET = 10;
82    final int LEGEND_Y_OFFSET = 0;
83    final int LEGEND_TEXT_OFFSET = 10;
84    final int LEGEND_FONT_SIZE = 24;
85    final int AXIS_NAME_FONT_SIZE = 24;
86  
87    protected boolean offline_use = true;
88    protected HttpServletRequest request;
89  
90    // for offline use only
91    // keys that need to be filled:
92    // period (last1/2/3/6/12/24hr,last7d,last30d), time_type (range/last), start, end
93    protected HashMap<String, String> param_map;
94    
95    protected String cluster;
96    protected String timezone;
97    protected String query_state;
98    protected String query_stat_type;
99    protected final String table = new String("filesystem_fsm");
100   protected boolean plot_legend = false; // controls whether to plot hostnames
101   protected boolean sort_nodes = true;
102   protected boolean plot_additional_info = true;
103   protected String add_info_extra = null;
104   
105   protected Display dis;
106   protected Visualization viz;
107   
108   protected Rectangle2D dataBound = new Rectangle2D.Double();
109   protected Rectangle2D xlabBound = new Rectangle2D.Double();
110   protected Rectangle2D ylabBound = new Rectangle2D.Double();
111   protected Rectangle2D labelBottomBound = new Rectangle2D.Double();
112   
113   protected HashMap<String, String> prettyStateNames;
114   
115   /* Different group names allow control of what Renderers to use */
116   final String maingroup = "Data";
117   final String othergroup = "Misc";
118   final String labelgroup = "Label";
119   final String legendgroup = "Legend";
120   final String legendshapegroup = "LegendShape";
121   final String addinfogroup = "AddInfo";
122   final String addinfoshapegroup = "AddInfoShape";
123   
124   public Heatmap() {
125     this.cluster = new String("");
126     this.timezone = new String("");
127     this.query_state = new String("");
128     this.query_stat_type = new String("");
129     param_map = new HashMap<String, String>();    
130   }
131   
132   /**
133    * @brief Constructor for Swimlanes visualization object
134    * @param timezone Timezone string from environment
135    * @param cluster Cluster name from environment
136    * @param event_type Whether to display shuffles or not
137    * @param valmap HashMap of key/value pairs simulating parameters from a HttpRequest
138    */
139   public Heatmap
140     (String timezone, String cluster, String event_type, 
141      String query_stat_type,
142      HashMap<String, String> valmap) 
143   {
144     this.cluster = new String(cluster);
145     if (timezone != null) {
146       this.timezone = new String(timezone);
147     } else {
148       this.timezone = null;
149     }    
150     this.query_state = new String(event_type);
151     this.query_stat_type = new String(query_stat_type);
152 
153     /* This should "simulate" an HttpServletRequest
154      * Need to have "start" and "end" in seconds since Epoch
155      */
156     this.param_map = valmap; 
157   }
158   
159   public Heatmap
160     (String timezone, String cluster, String query_state, 
161      String query_stat_type,
162      HashMap<String, String> valmap, String shuffles) 
163   {
164     
165     this.cluster = new String(cluster);
166     if (timezone != null) {
167       this.timezone = new String(timezone);
168     } else {
169       this.timezone = null;
170     }
171     this.query_state = new String(query_state);
172     this.query_stat_type = new String(query_stat_type);
173 
174     /* This should "simulate" an HttpServletRequest
175      * Need to have "start" and "end" in seconds since Epoch
176      */
177     this.param_map = valmap; 
178     
179   }
180   
181   public Heatmap
182     (String timezone, String cluster, String query_state, 
183      String query_stat_type,
184      HashMap<String, String> valmap, 
185      int w, int h) 
186   {
187     
188     this.cluster = new String(cluster);
189     if (timezone != null) {
190       this.timezone = new String(timezone);
191     } else {
192       this.timezone = null;
193     }
194     this.query_state = new String(query_state);
195     this.query_stat_type = new String(query_stat_type);
196 
197     /* This should "simulate" an HttpServletRequest
198      * Need to have "start" and "end" in seconds since Epoch
199      */
200     this.param_map = valmap; 
201         
202     this.SIZE_X = w;
203     this.SIZE_Y = h;
204   }
205   
206   public Heatmap(HttpServletRequest request) {
207     XssFilter xf = new XssFilter(request);
208     this.offline_use = false;
209     this.request = request;
210     HttpSession session = request.getSession();
211     this.cluster = session.getAttribute("cluster").toString();
212     String query_state = xf.getParameter("query_state");
213     if (query_state != null) {
214       this.query_state = new String(query_state);
215     } else {
216       this.query_state = new String("read");
217     }
218     String query_stat_type = xf.getParameter("query_stat_type");
219     if (query_stat_type != null) {
220       this.query_stat_type = new String(query_stat_type);
221     } else {
222       this.query_stat_type = new String("transaction_count");
223     }
224     this.timezone = session.getAttribute("time_zone").toString();
225   }
226 
227   /**
228    * Set dimensions of image to be generated
229    * Call before calling @see #run
230    */
231   public void setDimensions(int width, int height) {
232     this.SIZE_X=width;
233     this.SIZE_Y=height;
234   }
235   
236   /**
237    * Specify whether to print labels of hosts along axes
238    * Call before calling @see #run
239    */
240   public void setLegend(boolean legendopt) {
241     if (legendopt) {
242       this.plot_legend = true;
243     } else {
244       this.plot_legend = false;
245     }
246   }
247   
248   
249   /**
250    * Generates image in specified format, and writes image as binary
251    * output to supplied output stream 
252    */
253   public boolean getImage(java.io.OutputStream output, String img_fmt, double scale) {
254     dis = new Display(this.viz);
255     dis.setSize(SIZE_X,SIZE_Y);
256     dis.setHighQuality(true);
257     dis.setFont(new Font(Font.SANS_SERIF,Font.PLAIN,24));
258     return dis.saveImage(output, img_fmt, scale);
259   } 
260   
261   protected void setupRenderer() {
262     this.viz.setRendererFactory(new RendererFactory(){
263       AbstractShapeRenderer sr = new ShapeRenderer();
264       ShapeRenderer sr_big = new ShapeRenderer(BOXWIDTH);
265       Renderer arY = new AxisRenderer(Constants.LEFT, Constants.TOP);
266       Renderer arX = new AxisRenderer(Constants.CENTER, Constants.BOTTOM);
267       PolygonRenderer pr = new PolygonRenderer(Constants.POLY_TYPE_LINE);
268       LabelRenderer lr = new LabelRenderer("label");
269       LabelRenderer lr_legend = new LabelRenderer("label");
270             
271       public Renderer getRenderer(VisualItem item) {
272         lr_legend.setHorizontalAlignment(Constants.LEFT);
273         lr_legend.setVerticalAlignment(Constants.CENTER);
274         lr.setHorizontalAlignment(Constants.CENTER);
275         lr.setVerticalAlignment(Constants.CENTER);
276         if (item.isInGroup(maingroup)) {
277           return sr_big;
278         } else if (item.isInGroup(legendgroup)) {
279           return lr_legend;
280         } else if (item.isInGroup(addinfogroup)) {
281           return lr;
282         }
283         return sr;
284       }
285     });
286   }
287   
288   // setup columns: add additional time fields
289   protected HeatmapData setupDataTable() {
290     HeatmapData hd = this.getData();    
291     return hd;
292   }
293   
294   protected void setupHeatmap(VisualTable vtab, HeatmapData hd) 
295   {
296     long [][] stats = hd.stats;
297     int i, j, curr_idx;
298     long curr_val;
299     int num_hosts = hd.num_hosts;
300     ColorMap cm = new ColorMap(
301       ColorLib.getInterpolatedPalette(
302         ColorLib.color(ColorLib.getColor(32,0,0)),
303         ColorLib.color(Color.WHITE)
304       ),
305       (double)hd.min,(double)hd.max
306     );
307     
308     for (i = 0; i < num_hosts; i++) {
309       for (j = 0; j < num_hosts; j++) {
310         curr_idx = j+(i*num_hosts);
311         curr_val = stats[i][j]; 
312         if (curr_val >= hd.min) {
313           vtab.setFillColor(curr_idx, cm.getColor((double)curr_val));
314         } else if (curr_val == 0) {
315           vtab.setFillColor(curr_idx, ColorLib.color(Color.BLACK));
316         }
317       }
318     }
319     
320     // gridlayout puts tiles on row-wise (row1, followed by row2, etc.)
321     GridLayout gl = new GridLayout(maingroup, num_hosts, num_hosts);
322     gl.setLayoutBounds(this.dataBound);
323     ActionList gl_list = new ActionList();
324     gl_list.add(gl);
325     this.viz.putAction("gridlayout",gl_list);
326     this.viz.run("gridlayout");
327   }
328   
329   protected void addHostLabels(HeatmapData hd) {
330     Table legend_labels_table = new Table();
331     legend_labels_table.addColumn("label",String.class);
332     legend_labels_table.addRows(hd.hostnames.length);
333     for (int i = 0; i < hd.hostnames.length; i++) {
334       legend_labels_table.setString(i,"label",hd.hostnames[i]);
335     }
336     float start_x = LEGEND_X_OFFSET;
337     float start_y = LEGEND_Y_OFFSET + BORDER[1] + (BOXWIDTH/2);    
338     float incr = this.BOXWIDTH;
339     VisualTable legend_labels_table_viz = this.viz.addTable(legendgroup, legend_labels_table);
340     for (int i = 0; i < hd.hostnames.length; i++) {
341       legend_labels_table_viz.setFloat(i, VisualItem.X, start_x + LEGEND_TEXT_OFFSET);
342       legend_labels_table_viz.setFloat(i, VisualItem.Y, start_y + (i * incr));
343       legend_labels_table_viz.setTextColor(i,ColorLib.color(java.awt.Color.BLACK));
344       legend_labels_table_viz.setFont(i,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
345     } 
346   }
347   
348   protected void addAddlInfo(HeatmapData hd) {
349     Table legend_labels_table = new Table();
350     legend_labels_table.addColumn("label",String.class);
351     legend_labels_table.addRows(3);
352     
353     String hostnumstring = "Number of hosts: " + hd.num_hosts;
354     if (sort_nodes) {
355       hostnumstring += " (nodes sorted)";
356     } else {
357       hostnumstring += " (nodes not sorted)";
358     }
359     if (add_info_extra != null) hostnumstring += add_info_extra;
360     legend_labels_table.setString(0,"label",hostnumstring);
361     legend_labels_table.setString(1,"label","Src. Hosts");
362     legend_labels_table.setString(2,"label","Dest. Hosts");
363     
364     VisualTable legend_labels_table_viz = this.viz.addTable(addinfogroup, legend_labels_table);
365 
366     legend_labels_table_viz.setFloat(0, VisualItem.X, this.SIZE_X/2);
367     legend_labels_table_viz.setFloat(0, VisualItem.Y, BORDER[1]/2);
368     legend_labels_table_viz.setTextColor(0,ColorLib.color(java.awt.Color.BLACK));
369     legend_labels_table_viz.setFont(0,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
370 
371     legend_labels_table_viz.setFloat(1, VisualItem.X, this.SIZE_X/2);
372     legend_labels_table_viz.setFloat(1, VisualItem.Y, BORDER[1] + (BOXWIDTH*hd.num_hosts) + BORDER[3]/2);
373     legend_labels_table_viz.setTextColor(1,ColorLib.color(java.awt.Color.BLACK));
374     legend_labels_table_viz.setFont(1,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
375 
376     legend_labels_table_viz.setFloat(2, VisualItem.X, BORDER[0] + (BOXWIDTH*hd.num_hosts) + BORDER[2]/2);
377     legend_labels_table_viz.setFloat(2, VisualItem.Y, this.SIZE_Y/2);
378     legend_labels_table_viz.setTextColor(2,ColorLib.color(java.awt.Color.BLACK));
379     legend_labels_table_viz.setFont(2,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
380 
381   }
382   
383   protected void initPrettyNames() {
384     this.prettyStateNames = new HashMap<String, String>();
385 
386     prettyStateNames.put("read","Block Reads");
387     prettyStateNames.put("write","Block Writes");
388     prettyStateNames.put("read_local", "Local Block Reads");
389     prettyStateNames.put("write_local", "Local Block Writes");
390     prettyStateNames.put("read_remote", "Remote Block Reads");
391     prettyStateNames.put("write_remote", "Remote Block Writes");
392     prettyStateNames.put("write_replicated", "Replicated Block Writes");    
393   }
394   
395   /**
396    * Actual code that calls data, generates heatmap, and saves it
397    */
398   public void run() {
399     initPrettyNames();
400     
401     // setup visualization
402     this.viz = new Visualization();
403     
404     // add table to visualization
405     HeatmapData hd = this.setupDataTable();
406 
407     // setup bounds
408     int width;
409     if (SIZE_X-BORDER[0]-BORDER[2] < SIZE_Y-BORDER[1]-BORDER[3]) {
410       BOXWIDTH = (SIZE_X-BORDER[0]-BORDER[2]) / hd.num_hosts;
411     } else {
412       BOXWIDTH = (SIZE_Y-BORDER[1]-BORDER[3]) / hd.num_hosts;
413     }
414     width = hd.num_hosts * BOXWIDTH;
415     this.dataBound.setRect(
416       BORDER[0]+BOXWIDTH/2,
417       BORDER[1]+BOXWIDTH/2,
418       width-BOXWIDTH,width-BOXWIDTH
419     );
420     this.SIZE_X = BORDER[0] + BORDER[2] + (hd.num_hosts * BOXWIDTH);
421     this.SIZE_Y = BORDER[1] + BORDER[3] + (hd.num_hosts * BOXWIDTH);
422     
423     log.debug("width total: " + width + " width per state: " + BOXWIDTH + " xstart: " 
424       + (BORDER[0]+BOXWIDTH/2) 
425       + " ystart: " + (BORDER[1]+BOXWIDTH/2) + " (num hosts: "+hd.num_hosts+")");
426     log.debug("X size: " + this.SIZE_X + " Y size: " + this.SIZE_Y);
427     
428     this.setupRenderer();
429     VisualTable data_tab_viz = viz.addTable(maingroup, hd.agg_tab);
430     setupHeatmap(data_tab_viz, hd);
431     
432     ShapeAction legend_sa1 = null, legend_sa2 = null;
433     SpecifiedLayout legendlabels_sl1 = null, legendlabels_sl2 = null;
434     
435     if (plot_legend) {
436       addHostLabels(hd);
437       legend_sa1 = new ShapeAction(legendshapegroup);
438       legendlabels_sl1 = new SpecifiedLayout(legendgroup, VisualItem.X, VisualItem.Y);
439       ActionList legenddraw = new ActionList();
440       legenddraw.add(legend_sa1);
441       this.viz.putAction(legendshapegroup, legenddraw);
442       ActionList legendlabelsdraw = new ActionList();
443       legendlabelsdraw.add(legendlabels_sl1);
444       this.viz.putAction(legendgroup,legendlabelsdraw);
445     }
446 
447     if (plot_additional_info) {
448       addAddlInfo(hd);
449       legend_sa2 = new ShapeAction(addinfoshapegroup);
450       legendlabels_sl2 = new SpecifiedLayout(addinfogroup, VisualItem.X, VisualItem.Y);    
451       ActionList legenddraw = new ActionList();
452       legenddraw.add(legend_sa2);
453       this.viz.putAction(addinfoshapegroup, legenddraw);
454       ActionList legendlabelsdraw = new ActionList();
455       legendlabelsdraw.add(legendlabels_sl2);
456       this.viz.putAction(addinfogroup,legendlabelsdraw);
457     }
458 
459   }
460   
461   protected boolean checkDone(int [] clustId) {
462     for (int i = 1; i < clustId.length; i++) {
463       if (clustId[i] != clustId[0]) return false;
464     }
465     return true;
466   }
467   
468   /**
469    * Sort data for better visualization of patterns
470    */
471   protected int [] hClust (long [][] stat) 
472   {
473     int statlen = stat.length;
474     long [] rowSums = new long[statlen];
475     int [] permute = new int[statlen];
476     int i,j;
477 
478     // initialize permutation
479     for (i = 0; i < statlen; i++) {
480       permute[i] = i;
481     }
482     
483     for (i = 0; i < statlen; i++) {
484       rowSums[i] = 0;
485       for (j = 0; j < statlen; j++) {
486         rowSums[i] += stat[i][j];
487       }
488     }
489     
490     // insertion sort
491     for (i = 0; i < statlen-1; i++) {
492       long val = rowSums[i];
493       int thispos = permute[i];
494       j = i-1;
495       while (j >= 0 && rowSums[j] > val) {
496         rowSums[j+1] = rowSums[j];
497         permute[j+1] = permute[j];
498         j--;
499       }
500       rowSums[j+1] = val; 
501       permute[j+1] = thispos;
502     }
503     
504     return permute;
505       
506   }
507   
508   /**
509    * Reorder rows (and columns) according to a given ordering
510    * Maintains same ordering along rows and columns
511    */
512   protected long [][] doPermute (long [][] stat, int [] permute) {
513     int statlen = stat.length;
514     int i, j, curr_pos;
515     long [][] stat2 = new long[statlen][statlen];
516     
517     assert(stat.length == permute.length);
518     
519     for (i = 0; i < statlen; i++) {
520       curr_pos = permute[i];
521       for (j = 0; j < statlen; j++) {
522         stat2[i][j] = stat[curr_pos][permute[j]];
523       }
524     }
525     
526     return stat2;
527   }
528   
529   /**
530    * Interfaces with database to get data and 
531    * populate data structures for rendering
532    */
533   public HeatmapData getData() {
534     // preliminary setup
535     OfflineTimeHandler time_offline;
536     TimeHandler time_online;
537     long start, end, min, max;
538     
539     if (offline_use) {
540       time_offline = new OfflineTimeHandler(param_map, this.timezone);
541       start = time_offline.getStartTime();
542       end = time_offline.getEndTime();
543     } else {
544       time_online = new TimeHandler(this.request, this.timezone);
545       start = time_online.getStartTime();
546       end = time_online.getEndTime();
547     }
548     
549     DatabaseWriter dbw = new DatabaseWriter(this.cluster);
550     
551     // setup query
552     String query;
553     if (this.query_state != null && this.query_state.equals("read")) {
554       query = "select block_id,start_time,finish_time,start_time_millis,finish_time_millis,status,state_name,hostname,other_host,bytes from ["+table+"] where finish_time between '[start]' and '[end]' and (state_name like 'read_local' or state_name like 'read_remote')";
555     } else if (this.query_state != null && this.query_state.equals("write")) {
556       query = "select block_id,start_time,finish_time,start_time_millis,finish_time_millis,status,state_name,hostname,other_host,bytes from ["+table+"] where finish_time between '[start]' and '[end]' and (state_name like 'write_local' or state_name like 'write_remote' or state_name like 'write_replicated')";
557     } else {
558       query = "select block_id,start_time,finish_time,start_time_millis,finish_time_millis,status,state_name,hostname,other_host,bytes from ["+table+"] where finish_time between '[start]' and '[end]' and state_name like '" + query_state + "'";
559     } 
560     Macro mp = new Macro(start,end,query);
561     query = mp.toString() + " order by start_time";
562     
563     ArrayList<HashMap<String, Object>> events = new ArrayList<HashMap<String, Object>>();
564 
565     ResultSet rs = null;
566     
567     log.debug("Query: " + query);
568     // run query, extract results
569     try {
570       rs = dbw.query(query);
571       ResultSetMetaData rmeta = rs.getMetaData();
572       int col = rmeta.getColumnCount();
573       while (rs.next()) {
574         HashMap<String, Object> event = new HashMap<String, Object>();
575         for(int i=1;i<=col;i++) {
576           if(rmeta.getColumnType(i)==java.sql.Types.TIMESTAMP) {
577             event.put(rmeta.getColumnName(i),rs.getTimestamp(i).getTime());
578           } else {
579             event.put(rmeta.getColumnName(i),rs.getString(i));
580           }
581         }
582         events.add(event);
583       }
584     } catch (SQLException ex) {
585       // handle any errors
586       log.error("SQLException: " + ex.getMessage());
587       log.error("SQLState: " + ex.getSQLState());
588       log.error("VendorError: " + ex.getErrorCode());
589     } finally {
590       dbw.close();
591     }    
592 
593     log.info(events.size() + " results returned.");
594 
595     HashSet<String> host_set = new HashSet<String>();
596     HashMap<String, Integer> host_indices = new HashMap<String, Integer>();
597     HashMap<Integer, String> host_rev_indices = new HashMap<Integer, String>();
598 
599     // collect hosts, name unique hosts
600     for(int i = 0; i < events.size(); i++) {
601       HashMap<String, Object> event = events.get(i);
602       String curr_host = (String) event.get("hostname");
603       String other_host = (String) event.get("other_host");
604       host_set.add(curr_host);
605       host_set.add(other_host);
606     }
607     int num_hosts = host_set.size();
608 
609     Iterator<String> host_iter = host_set.iterator();
610     for (int i = 0; i < num_hosts && host_iter.hasNext(); i++) {
611       String curr_host = host_iter.next();
612       host_indices.put(curr_host, new Integer(i));
613       host_rev_indices.put(new Integer(i),curr_host);
614     }
615 
616     System.out.println("Number of hosts: " + num_hosts);
617     long stats[][] = new long[num_hosts][num_hosts];
618     long count[][] = new long[num_hosts][num_hosts]; // used for averaging
619 
620     int start_millis = 0, end_millis = 0;
621 
622     // deliberate design choice to duplicate code PER possible operation
623     // otherwise we have to do the mode check N times, for N states returned
624     //
625     // compute aggregate statistics
626     log.info("Query statistic type: "+this.query_stat_type);
627     if (this.query_stat_type.equals("transaction_count")) {
628       for(int i=0;i<events.size();i++) {
629         HashMap<String, Object> event = events.get(i);
630         start=(Long)event.get("start_time");
631         end=(Long)event.get("finish_time");
632         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
633         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
634         String this_host = (String) event.get("hostname");
635         String other_host = (String) event.get("other_host");
636         int this_host_idx = host_indices.get(this_host).intValue();
637         int other_host_idx = host_indices.get(other_host).intValue();
638 
639         // to, from
640         stats[other_host_idx][this_host_idx] += 1;
641       }
642     } else if (this.query_stat_type.equals("avg_duration")) {
643       for(int i=0;i<events.size();i++) {
644         HashMap<String, Object> event = events.get(i);
645         start=(Long)event.get("start_time");
646         end=(Long)event.get("finish_time");
647         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
648         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
649         String this_host = (String) event.get("hostname");
650         String other_host = (String) event.get("other_host");
651         int this_host_idx = host_indices.get(this_host).intValue();
652         int other_host_idx = host_indices.get(other_host).intValue();
653 
654         long curr_val = end_millis - start_millis + ((end - start)*1000);
655 
656         // to, from
657         stats[other_host_idx][this_host_idx] += curr_val;
658         count[other_host_idx][this_host_idx] += 1;
659       }    
660       for (int i = 0; i < num_hosts; i++) {
661         for (int j = 0; j < num_hosts; j++) {
662           if (count[i][j] > 0) stats[i][j] = stats[i][j] / count[i][j];
663         }
664       }
665     } else if (this.query_stat_type.equals("avg_volume")) {
666       for(int i=0;i<events.size();i++) {
667         HashMap<String, Object> event = events.get(i);
668         start=(Long)event.get("start_time");
669         end=(Long)event.get("finish_time");
670         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
671         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
672         String this_host = (String) event.get("hostname");
673         String other_host = (String) event.get("other_host");
674         int this_host_idx = host_indices.get(this_host).intValue();
675         int other_host_idx = host_indices.get(other_host).intValue();
676 
677         long curr_val = Long.parseLong((String)event.get("bytes"));
678 
679         // to, from
680         stats[other_host_idx][this_host_idx] += curr_val;
681         count[other_host_idx][this_host_idx] += 1;
682       }    
683       for (int i = 0; i < num_hosts; i++) {
684         for (int j = 0; j < num_hosts; j++) {
685           if (count[i][j] > 0) stats[i][j] = stats[i][j] / count[i][j];
686         }
687       }
688     } else if (this.query_stat_type.equals("total_duration")) {
689       for(int i=0;i<events.size();i++) {
690         HashMap<String, Object> event = events.get(i);
691         start=(Long)event.get("start_time");
692         end=(Long)event.get("finish_time");
693         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
694         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
695         String this_host = (String) event.get("hostname");
696         String other_host = (String) event.get("other_host");
697         int this_host_idx = host_indices.get(this_host).intValue();
698         int other_host_idx = host_indices.get(other_host).intValue();
699 
700         double curr_val = end_millis - start_millis + ((end - start)*1000);
701 
702         // to, from
703         stats[other_host_idx][this_host_idx] += curr_val;
704       } 
705     } else if (this.query_stat_type.equals("total_volume")) {
706       for(int i=0;i<events.size();i++) {
707         HashMap<String, Object> event = events.get(i);
708         start=(Long)event.get("start_time");
709         end=(Long)event.get("finish_time");
710         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
711         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
712         String this_host = (String) event.get("hostname");
713         String other_host = (String) event.get("other_host");
714         int this_host_idx = host_indices.get(this_host).intValue();
715         int other_host_idx = host_indices.get(other_host).intValue();
716 
717         long curr_val = Long.parseLong((String)event.get("bytes"));
718 
719         // to, from
720         stats[other_host_idx][this_host_idx] += curr_val;
721       }    
722     }
723     
724     int [] permute = null;
725     if (sort_nodes) {
726       permute = hClust(stats);
727       stats = doPermute(stats,permute);
728     }
729     
730     Table agg_tab = new Table();
731     agg_tab.addColumn("stat", long.class);
732     min = Long.MAX_VALUE;
733     max = Long.MIN_VALUE;
734     agg_tab.addRows(num_hosts*num_hosts);
735     
736     // row-wise placement (row1, followed by row2, etc.)
737     for (int i = 0; i < num_hosts; i++) {
738       for (int j = 0; j < num_hosts; j++) {
739         agg_tab.setLong((i*num_hosts)+j,"stat",stats[i][j]);
740         if (stats[i][j] > max) max = stats[i][j];
741         if (stats[i][j] > 0 && stats[i][j] < min) min = stats[i][j];
742       }
743     }
744     if (min == Long.MAX_VALUE) min = 0;
745     
746     log.info(agg_tab);
747     
748     // collate data
749     HeatmapData hd = new HeatmapData();
750     hd.stats = new long[num_hosts][num_hosts];
751     hd.stats = stats;
752     hd.min = min;
753     hd.max = max;
754     hd.num_hosts = num_hosts;
755     hd.agg_tab = agg_tab;
756     
757     this.add_info_extra = new String("\nState: "+this.prettyStateNames.get(this.query_state)+
758       " ("+events.size()+" "+this.query_state+"'s ["+this.query_stat_type+"])\n" + 
759       "Plotted value range: ["+hd.min+","+hd.max+"] (Zeros in black)");
760 
761     hd.hostnames = new String [num_hosts];
762     for (int i = 0; i < num_hosts; i++) {
763       String curr_host = host_rev_indices.get(new Integer(permute[i]));
764       if (sort_nodes) {
765         hd.hostnames[i] = new String(curr_host);
766       } else {
767         hd.hostnames[i] = new String(curr_host);
768       }
769     }
770     
771     return hd;
772   }
773   
774 }