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 = "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 = "";
126     this.timezone = "";
127     this.query_state = "";
128     this.query_stat_type = "";
129     param_map = new HashMap<String, String>();
130   }
131   
132   /**
133    * 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 query_stat_type 
138    * @param valmap HashMap of key/value pairs simulating parameters from a HttpRequest
139    */
140   public Heatmap
141     (String timezone, String cluster, String event_type, 
142      String query_stat_type,
143      HashMap<String, String> valmap) 
144   {
145     this.cluster = cluster;
146     if (timezone != null) {
147       this.timezone = timezone;
148     } else {
149       this.timezone = null;
150     }    
151     this.query_state = event_type;
152     this.query_stat_type = query_stat_type;
153 
154     /* This should "simulate" an HttpServletRequest
155      * Need to have "start" and "end" in seconds since Epoch
156      */
157     this.param_map = valmap; 
158   }
159   
160   public Heatmap
161     (String timezone, String cluster, String query_state, 
162      String query_stat_type,
163      HashMap<String, String> valmap, String shuffles) 
164   {
165     
166     this.cluster = cluster;
167     if (timezone != null) {
168       this.timezone = timezone;
169     } else {
170       this.timezone = null;
171     }
172     this.query_state = query_state;
173     this.query_stat_type = query_stat_type;
174 
175     /* This should "simulate" an HttpServletRequest
176      * Need to have "start" and "end" in seconds since Epoch
177      */
178     this.param_map = valmap; 
179     
180   }
181   
182   public Heatmap
183     (String timezone, String cluster, String query_state, 
184      String query_stat_type,
185      HashMap<String, String> valmap, 
186      int w, int h) 
187   {
188     
189     this.cluster = cluster;
190     if (timezone != null) {
191       this.timezone = timezone;
192     } else {
193       this.timezone = null;
194     }
195     this.query_state = query_state;
196     this.query_stat_type = query_stat_type;
197 
198     /* This should "simulate" an HttpServletRequest
199      * Need to have "start" and "end" in seconds since Epoch
200      */
201     this.param_map = valmap; 
202         
203     this.SIZE_X = w;
204     this.SIZE_Y = h;
205   }
206   
207   public Heatmap(HttpServletRequest request) {
208     XssFilter xf = new XssFilter(request);
209     this.offline_use = false;
210     this.request = request;
211     HttpSession session = request.getSession();
212     this.cluster = session.getAttribute("cluster").toString();
213     String query_state = xf.getParameter("query_state");
214     if (query_state != null) {
215       this.query_state = query_state;
216     } else {
217       this.query_state = "read";
218     }
219     String query_stat_type = xf.getParameter("query_stat_type");
220     if (query_stat_type != null) {
221       this.query_stat_type = query_stat_type;
222     } else {
223       this.query_stat_type = "transaction_count";
224     }
225     this.timezone = session.getAttribute("time_zone").toString();
226   }
227 
228   /**
229    * Set dimensions of image to be generated
230    * Call before calling @see #run
231    * @param width 
232    * @param height 
233    */
234   public void setDimensions(int width, int height) {
235     this.SIZE_X=width;
236     this.SIZE_Y=height;
237   }
238   
239   /**
240    * Specify whether to print labels of hosts along axes
241    * Call before calling @see #run
242    * @param legendopt 
243    */
244   public void setLegend(boolean legendopt) {
245     if (legendopt) {
246       this.plot_legend = true;
247     } else {
248       this.plot_legend = false;
249     }
250   }
251   
252   
253   /**
254    * Generates image in specified format, and writes image as binary
255    * output to supplied output stream 
256    * @param output 
257    * @param img_fmt 
258    * @param scale 
259    * @return 
260    */
261   public boolean getImage(java.io.OutputStream output, String img_fmt, double scale) {
262     dis = new Display(this.viz);
263     dis.setSize(SIZE_X,SIZE_Y);
264     dis.setHighQuality(true);
265     dis.setFont(new Font(Font.SANS_SERIF,Font.PLAIN,24));
266     return dis.saveImage(output, img_fmt, scale);
267   } 
268   
269   protected void setupRenderer() {
270     this.viz.setRendererFactory(new RendererFactory(){
271       AbstractShapeRenderer sr = new ShapeRenderer();
272       ShapeRenderer sr_big = new ShapeRenderer(BOXWIDTH);
273       LabelRenderer lr = new LabelRenderer("label");
274       LabelRenderer lr_legend = new LabelRenderer("label");
275             
276       public Renderer getRenderer(VisualItem item) {
277         lr_legend.setHorizontalAlignment(Constants.LEFT);
278         lr_legend.setVerticalAlignment(Constants.CENTER);
279         lr.setHorizontalAlignment(Constants.CENTER);
280         lr.setVerticalAlignment(Constants.CENTER);
281         if (item.isInGroup(maingroup)) {
282           return sr_big;
283         } else if (item.isInGroup(legendgroup)) {
284           return lr_legend;
285         } else if (item.isInGroup(addinfogroup)) {
286           return lr;
287         }
288         return sr;
289       }
290     });
291   }
292   
293   // setup columns: add additional time fields
294   protected HeatmapData setupDataTable() {
295     HeatmapData hd = this.getData();    
296     return hd;
297   }
298   
299   protected void setupHeatmap(VisualTable vtab, HeatmapData hd) 
300   {
301     long [][] stats = hd.stats;
302     int i, j, curr_idx;
303     long curr_val;
304     int num_hosts = hd.num_hosts;
305     ColorMap cm = new ColorMap(
306       ColorLib.getInterpolatedPalette(
307         ColorLib.color(ColorLib.getColor(32,0,0)),
308         ColorLib.color(Color.WHITE)
309       ),
310       (double)hd.min,(double)hd.max
311     );
312     
313     for (i = 0; i < num_hosts; i++) {
314       for (j = 0; j < num_hosts; j++) {
315         curr_idx = j+(i*num_hosts);
316         curr_val = stats[i][j]; 
317         if (curr_val >= hd.min) {
318           vtab.setFillColor(curr_idx, cm.getColor((double)curr_val));
319         } else if (curr_val == 0) {
320           vtab.setFillColor(curr_idx, ColorLib.color(Color.BLACK));
321         }
322       }
323     }
324     
325     // gridlayout puts tiles on row-wise (row1, followed by row2, etc.)
326     GridLayout gl = new GridLayout(maingroup, num_hosts, num_hosts);
327     gl.setLayoutBounds(this.dataBound);
328     ActionList gl_list = new ActionList();
329     gl_list.add(gl);
330     this.viz.putAction("gridlayout",gl_list);
331     this.viz.run("gridlayout");
332   }
333   
334   protected void addHostLabels(HeatmapData hd) {
335     Table legend_labels_table = new Table();
336     legend_labels_table.addColumn("label",String.class);
337     legend_labels_table.addRows(hd.hostnames.length);
338     for (int i = 0; i < hd.hostnames.length; i++) {
339       legend_labels_table.setString(i,"label",hd.hostnames[i]);
340     }
341     float start_x = LEGEND_X_OFFSET;
342     float start_y = LEGEND_Y_OFFSET + BORDER[1] + (BOXWIDTH/2);    
343     float incr = this.BOXWIDTH;
344     VisualTable legend_labels_table_viz = this.viz.addTable(legendgroup, legend_labels_table);
345     for (int i = 0; i < hd.hostnames.length; i++) {
346       legend_labels_table_viz.setFloat(i, VisualItem.X, start_x + LEGEND_TEXT_OFFSET);
347       legend_labels_table_viz.setFloat(i, VisualItem.Y, start_y + (i * incr));
348       legend_labels_table_viz.setTextColor(i,ColorLib.color(java.awt.Color.BLACK));
349       legend_labels_table_viz.setFont(i,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
350     } 
351   }
352   
353   protected void addAddlInfo(HeatmapData hd) {
354     Table legend_labels_table = new Table();
355     legend_labels_table.addColumn("label",String.class);
356     legend_labels_table.addRows(3);
357     
358     String hostnumstring = "Number of hosts: " + hd.num_hosts;
359     if (sort_nodes) {
360       hostnumstring += " (nodes sorted)";
361     } else {
362       hostnumstring += " (nodes not sorted)";
363     }
364     if (add_info_extra != null) hostnumstring += add_info_extra;
365     legend_labels_table.setString(0,"label",hostnumstring);
366     legend_labels_table.setString(1,"label","Src. Hosts");
367     legend_labels_table.setString(2,"label","Dest. Hosts");
368     
369     VisualTable legend_labels_table_viz = this.viz.addTable(addinfogroup, legend_labels_table);
370 
371     legend_labels_table_viz.setFloat(0, VisualItem.X, this.SIZE_X/2f);
372     legend_labels_table_viz.setFloat(0, VisualItem.Y, BORDER[1]/2f);
373     legend_labels_table_viz.setTextColor(0,ColorLib.color(java.awt.Color.BLACK));
374     legend_labels_table_viz.setFont(0,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
375 
376     legend_labels_table_viz.setFloat(1, VisualItem.X, this.SIZE_X/2f);
377     legend_labels_table_viz.setFloat(1, VisualItem.Y, BORDER[1] + (BOXWIDTH*hd.num_hosts) + BORDER[3]/2f);
378     legend_labels_table_viz.setTextColor(1,ColorLib.color(java.awt.Color.BLACK));
379     legend_labels_table_viz.setFont(1,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
380 
381     legend_labels_table_viz.setFloat(2, VisualItem.X, BORDER[0] + (BOXWIDTH*hd.num_hosts) + BORDER[2]/2f);
382     legend_labels_table_viz.setFloat(2, VisualItem.Y, this.SIZE_Y/2f);
383     legend_labels_table_viz.setTextColor(2,ColorLib.color(java.awt.Color.BLACK));
384     legend_labels_table_viz.setFont(2,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE));
385 
386   }
387   
388   protected void initPrettyNames() {
389     this.prettyStateNames = new HashMap<String, String>();
390 
391     prettyStateNames.put("read","Block Reads");
392     prettyStateNames.put("write","Block Writes");
393     prettyStateNames.put("read_local", "Local Block Reads");
394     prettyStateNames.put("write_local", "Local Block Writes");
395     prettyStateNames.put("read_remote", "Remote Block Reads");
396     prettyStateNames.put("write_remote", "Remote Block Writes");
397     prettyStateNames.put("write_replicated", "Replicated Block Writes");    
398   }
399   
400   /**
401    * Actual code that calls data, generates heatmap, and saves it
402    */
403   public void run() {
404     initPrettyNames();
405     
406     // setup visualization
407     this.viz = new Visualization();
408     
409     // add table to visualization
410     HeatmapData hd = this.setupDataTable();
411 
412     // setup bounds
413     int width;
414     if (SIZE_X-BORDER[0]-BORDER[2] < SIZE_Y-BORDER[1]-BORDER[3]) {
415       BOXWIDTH = (SIZE_X-BORDER[0]-BORDER[2]) / hd.num_hosts;
416     } else {
417       BOXWIDTH = (SIZE_Y-BORDER[1]-BORDER[3]) / hd.num_hosts;
418     }
419     width = hd.num_hosts * BOXWIDTH;
420     this.dataBound.setRect(
421       BORDER[0]+BOXWIDTH/2,
422       BORDER[1]+BOXWIDTH/2,
423       width-BOXWIDTH,width-BOXWIDTH
424     );
425     this.SIZE_X = BORDER[0] + BORDER[2] + (hd.num_hosts * BOXWIDTH);
426     this.SIZE_Y = BORDER[1] + BORDER[3] + (hd.num_hosts * BOXWIDTH);
427     
428     log.debug("width total: " + width + " width per state: " + BOXWIDTH + " xstart: " 
429       + (BORDER[0]+BOXWIDTH/2) 
430       + " ystart: " + (BORDER[1]+BOXWIDTH/2) + " (num hosts: "+hd.num_hosts+")");
431     log.debug("X size: " + this.SIZE_X + " Y size: " + this.SIZE_Y);
432     
433     this.setupRenderer();
434     VisualTable data_tab_viz = viz.addTable(maingroup, hd.agg_tab);
435     setupHeatmap(data_tab_viz, hd);
436     
437     ShapeAction legend_sa1 = null, legend_sa2 = null;
438     SpecifiedLayout legendlabels_sl1 = null, legendlabels_sl2 = null;
439     
440     if (plot_legend) {
441       addHostLabels(hd);
442       legend_sa1 = new ShapeAction(legendshapegroup);
443       legendlabels_sl1 = new SpecifiedLayout(legendgroup, VisualItem.X, VisualItem.Y);
444       ActionList legenddraw = new ActionList();
445       legenddraw.add(legend_sa1);
446       this.viz.putAction(legendshapegroup, legenddraw);
447       ActionList legendlabelsdraw = new ActionList();
448       legendlabelsdraw.add(legendlabels_sl1);
449       this.viz.putAction(legendgroup,legendlabelsdraw);
450     }
451 
452     if (plot_additional_info) {
453       addAddlInfo(hd);
454       legend_sa2 = new ShapeAction(addinfoshapegroup);
455       legendlabels_sl2 = new SpecifiedLayout(addinfogroup, VisualItem.X, VisualItem.Y);    
456       ActionList legenddraw = new ActionList();
457       legenddraw.add(legend_sa2);
458       this.viz.putAction(addinfoshapegroup, legenddraw);
459       ActionList legendlabelsdraw = new ActionList();
460       legendlabelsdraw.add(legendlabels_sl2);
461       this.viz.putAction(addinfogroup,legendlabelsdraw);
462     }
463 
464   }
465   
466   protected boolean checkDone(int [] clustId) {
467     for (int i = 1; i < clustId.length; i++) {
468       if (clustId[i] != clustId[0]) return false;
469     }
470     return true;
471   }
472   
473   /**
474    * Sort data for better visualization of patterns
475    */
476   protected int [] hClust (long [][] stat) 
477   {
478     int statlen = stat.length;
479     long [] rowSums = new long[statlen];
480     int [] permute = new int[statlen];
481     int i,j;
482 
483     // initialize permutation
484     for (i = 0; i < statlen; i++) {
485       permute[i] = i;
486     }
487     
488     for (i = 0; i < statlen; i++) {
489       rowSums[i] = 0;
490       for (j = 0; j < statlen; j++) {
491         rowSums[i] += stat[i][j];
492       }
493     }
494     
495     // insertion sort
496     for (i = 0; i < statlen-1; i++) {
497       long val = rowSums[i];
498       int thispos = permute[i];
499       j = i-1;
500       while (j >= 0 && rowSums[j] > val) {
501         rowSums[j+1] = rowSums[j];
502         permute[j+1] = permute[j];
503         j--;
504       }
505       rowSums[j+1] = val; 
506       permute[j+1] = thispos;
507     }
508     
509     return permute;
510       
511   }
512   
513   /**
514    * Reorder rows (and columns) according to a given ordering
515    * Maintains same ordering along rows and columns
516    */
517   protected long [][] doPermute (long [][] stat, int [] permute) {
518     int statlen = stat.length;
519     int i, j, curr_pos;
520     long [][] stat2 = new long[statlen][statlen];
521     
522     assert(stat.length == permute.length);
523     
524     for (i = 0; i < statlen; i++) {
525       curr_pos = permute[i];
526       for (j = 0; j < statlen; j++) {
527         stat2[i][j] = stat[curr_pos][permute[j]];
528       }
529     }
530     
531     return stat2;
532   }
533   
534   /**
535    * Interfaces with database to get data and 
536    * populate data structures for rendering
537    * @return 
538    */
539   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value =
540       "SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE", 
541       justification = "Dynamic based upon tables in the database")
542   public HeatmapData getData() {
543     // preliminary setup
544     OfflineTimeHandler time_offline;
545     TimeHandler time_online;
546     long start, end, min, max;
547     
548     if (offline_use) {
549       time_offline = new OfflineTimeHandler(param_map, this.timezone);
550       start = time_offline.getStartTime();
551       end = time_offline.getEndTime();
552     } else {
553       time_online = new TimeHandler(this.request, this.timezone);
554       start = time_online.getStartTime();
555       end = time_online.getEndTime();
556     }
557     
558     DatabaseWriter dbw = new DatabaseWriter(this.cluster);
559     
560     // setup query
561     
562     String sqlTemplate = "select block_id,start_time,finish_time,start_time_millis,finish_time_millis,status,state_name,hostname,other_host,bytes from [%s] where finish_time between '[start]' and '[end]' and (%s) order by start_time";
563     String query;
564     if (this.query_state != null && this.query_state.equals("read")) {
565       query = String.format(sqlTemplate, table,"state_name like 'read_local' or state_name like 'read_remote'");
566     } else if (this.query_state != null && this.query_state.equals("write")) {
567       query = String.format(sqlTemplate, table, "state_name like 'write_local' or state_name like 'write_remote' or state_name like 'write_replicated'");
568     } else {
569       query = String.format(sqlTemplate, table, "state_name like '" + query_state + "'");
570     } 
571     Macro mp = new Macro(start,end,query);
572     String q = mp.toString();
573     
574     ArrayList<HashMap<String, Object>> events = new ArrayList<HashMap<String, Object>>();
575 
576     ResultSet rs = null;
577     
578     log.debug("Query: " + q);
579     // run query, extract results
580     try {
581       rs = dbw.query(q);
582       ResultSetMetaData rmeta = rs.getMetaData();
583       int col = rmeta.getColumnCount();
584       while (rs.next()) {
585         HashMap<String, Object> event = new HashMap<String, Object>();
586         for(int i=1;i<=col;i++) {
587           if(rmeta.getColumnType(i)==java.sql.Types.TIMESTAMP) {
588             event.put(rmeta.getColumnName(i),rs.getTimestamp(i).getTime());
589           } else {
590             event.put(rmeta.getColumnName(i),rs.getString(i));
591           }
592         }
593         events.add(event);
594       }
595     } catch (SQLException ex) {
596       // handle any errors
597       log.error("SQLException: " + ex.getMessage());
598       log.error("SQLState: " + ex.getSQLState());
599       log.error("VendorError: " + ex.getErrorCode());
600     } finally {
601       dbw.close();
602     }    
603 
604     log.info(events.size() + " results returned.");
605 
606     HashSet<String> host_set = new HashSet<String>();
607     HashMap<String, Integer> host_indices = new HashMap<String, Integer>();
608     HashMap<Integer, String> host_rev_indices = new HashMap<Integer, String>();
609 
610     // collect hosts, name unique hosts
611     for(int i = 0; i < events.size(); i++) {
612       HashMap<String, Object> event = events.get(i);
613       String curr_host = (String) event.get("hostname");
614       String other_host = (String) event.get("other_host");
615       host_set.add(curr_host);
616       host_set.add(other_host);
617     }
618     int num_hosts = host_set.size();
619 
620     Iterator<String> host_iter = host_set.iterator();
621     for (int i = 0; i < num_hosts && host_iter.hasNext(); i++) {
622       String curr_host = host_iter.next();
623       host_indices.put(curr_host, i);
624       host_rev_indices.put(i,curr_host);
625     }
626 
627     System.out.println("Number of hosts: " + num_hosts);
628     long stats[][] = new long[num_hosts][num_hosts];
629     long count[][] = new long[num_hosts][num_hosts]; // used for averaging
630 
631     int start_millis = 0, end_millis = 0;
632 
633     // deliberate design choice to duplicate code PER possible operation
634     // otherwise we have to do the mode check N times, for N states returned
635     //
636     // compute aggregate statistics
637     log.info("Query statistic type: "+this.query_stat_type);
638     if (this.query_stat_type.equals("transaction_count")) {
639       for(int i=0;i<events.size();i++) {
640         HashMap<String, Object> event = events.get(i);
641         start=(Long)event.get("start_time");
642         end=(Long)event.get("finish_time");
643         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
644         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
645         String this_host = (String) event.get("hostname");
646         String other_host = (String) event.get("other_host");
647         int this_host_idx = host_indices.get(this_host).intValue();
648         int other_host_idx = host_indices.get(other_host).intValue();
649 
650         // to, from
651         stats[other_host_idx][this_host_idx] += 1;
652       }
653     } else if (this.query_stat_type.equals("avg_duration")) {
654       for(int i=0;i<events.size();i++) {
655         HashMap<String, Object> event = events.get(i);
656         start=(Long)event.get("start_time");
657         end=(Long)event.get("finish_time");
658         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
659         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
660         String this_host = (String) event.get("hostname");
661         String other_host = (String) event.get("other_host");
662         int this_host_idx = host_indices.get(this_host).intValue();
663         int other_host_idx = host_indices.get(other_host).intValue();
664 
665         long curr_val = end_millis - start_millis + ((end - start)*1000);
666 
667         // to, from
668         stats[other_host_idx][this_host_idx] += curr_val;
669         count[other_host_idx][this_host_idx] += 1;
670       }    
671       for (int i = 0; i < num_hosts; i++) {
672         for (int j = 0; j < num_hosts; j++) {
673           if (count[i][j] > 0) stats[i][j] = stats[i][j] / count[i][j];
674         }
675       }
676     } else if (this.query_stat_type.equals("avg_volume")) {
677       for(int i=0;i<events.size();i++) {
678         HashMap<String, Object> event = events.get(i);
679         start=(Long)event.get("start_time");
680         end=(Long)event.get("finish_time");
681         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
682         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
683         String this_host = (String) event.get("hostname");
684         String other_host = (String) event.get("other_host");
685         int this_host_idx = host_indices.get(this_host).intValue();
686         int other_host_idx = host_indices.get(other_host).intValue();
687 
688         long curr_val = Long.parseLong((String)event.get("bytes"));
689 
690         // to, from
691         stats[other_host_idx][this_host_idx] += curr_val;
692         count[other_host_idx][this_host_idx] += 1;
693       }    
694       for (int i = 0; i < num_hosts; i++) {
695         for (int j = 0; j < num_hosts; j++) {
696           if (count[i][j] > 0) stats[i][j] = stats[i][j] / count[i][j];
697         }
698       }
699     } else if (this.query_stat_type.equals("total_duration")) {
700       for(int i=0;i<events.size();i++) {
701         HashMap<String, Object> event = events.get(i);
702         start=(Long)event.get("start_time");
703         end=(Long)event.get("finish_time");
704         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
705         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
706         String this_host = (String) event.get("hostname");
707         String other_host = (String) event.get("other_host");
708         int this_host_idx = host_indices.get(this_host).intValue();
709         int other_host_idx = host_indices.get(other_host).intValue();
710 
711         double curr_val = end_millis - start_millis + ((end - start)*1000);
712 
713         // to, from
714         stats[other_host_idx][this_host_idx] += curr_val;
715       } 
716     } else if (this.query_stat_type.equals("total_volume")) {
717       for(int i=0;i<events.size();i++) {
718         HashMap<String, Object> event = events.get(i);
719         start=(Long)event.get("start_time");
720         end=(Long)event.get("finish_time");
721         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
722         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
723         String this_host = (String) event.get("hostname");
724         String other_host = (String) event.get("other_host");
725         int this_host_idx = host_indices.get(this_host).intValue();
726         int other_host_idx = host_indices.get(other_host).intValue();
727 
728         long curr_val = Long.parseLong((String)event.get("bytes"));
729 
730         // to, from
731         stats[other_host_idx][this_host_idx] += curr_val;
732       }    
733     }
734     
735     int [] permute = null;
736     if (sort_nodes) {
737       permute = hClust(stats);
738       stats = doPermute(stats,permute);
739     }
740     
741     Table agg_tab = new Table();
742     agg_tab.addColumn("stat", long.class);
743     min = Long.MAX_VALUE;
744     max = Long.MIN_VALUE;
745     agg_tab.addRows(num_hosts*num_hosts);
746     
747     // row-wise placement (row1, followed by row2, etc.)
748     for (int i = 0; i < num_hosts; i++) {
749       for (int j = 0; j < num_hosts; j++) {
750         agg_tab.setLong((i*num_hosts)+j,"stat",stats[i][j]);
751         if (stats[i][j] > max) max = stats[i][j];
752         if (stats[i][j] > 0 && stats[i][j] < min) min = stats[i][j];
753       }
754     }
755     if (min == Long.MAX_VALUE) min = 0;
756     
757     log.info(agg_tab);
758     
759     // collate data
760     HeatmapData hd = new HeatmapData();
761     hd.stats = stats;
762     hd.min = min;
763     hd.max = max;
764     hd.num_hosts = num_hosts;
765     hd.agg_tab = agg_tab;
766     
767     this.add_info_extra = new StringBuilder().append("\nState: ").append(this.prettyStateNames.get(this.query_state)).
768       append(" (").append(events.size()).append(" ").append(this.query_state).
769       append("'s [").append(this.query_stat_type).append("])\n").
770       append("Plotted value range: [").append(hd.min).append(",").append(hd.max).
771       append("] (Zeros in black)").toString();
772 
773     hd.hostnames = new String [num_hosts];
774     for (int i = 0; i < num_hosts; i++) {
775       String curr_host = host_rev_indices.get(permute[i]);
776       hd.hostnames[i] = curr_host;
777     }
778     
779     return hd;
780   }
781   
782 }