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  import prefuse.data.io.sql.*;
22  import prefuse.data.expression.parser.*;
23  import prefuse.data.expression.*;
24  import prefuse.data.column.*;
25  import prefuse.data.query.*;
26  import prefuse.data.*;
27  import prefuse.action.*;
28  import prefuse.action.layout.*;
29  import prefuse.action.assignment.*;
30  import prefuse.visual.expression.*;
31  import prefuse.visual.*;
32  import prefuse.render.*;
33  import prefuse.util.*;
34  import prefuse.*;
35  
36  import org.apache.hadoop.chukwa.hicc.OfflineTimeHandler;
37  import org.apache.hadoop.chukwa.hicc.TimeHandler;
38  import org.apache.hadoop.chukwa.util.DatabaseWriter;
39  import org.apache.hadoop.chukwa.database.Macro;
40  import org.apache.hadoop.chukwa.util.XssFilter;
41  
42  import javax.servlet.http.*;
43  
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  
47  import java.sql.*;
48  import java.util.*;
49  
50  import java.awt.Font;
51  import java.awt.geom.Rectangle2D;
52  import java.awt.Color;
53  
54  /**
55   * Static image rendering for heatmap visualization of spatial HDFS 
56   * activity patterns for scalable rendering on front-end (web-browser)
57   * Handles database data retrieval, transforming data to form for 
58   * visualization elements, and initializing and calling visualization
59   * elements 
60   */
61  public class Heatmap {
62  
63    /**
64     * Internal representation of all data needed to render heatmap;
65     * data-handling code populates this data structure
66     */
67    protected static class HeatmapData {
68      public Table agg_tab;
69      public long [][] stats;
70      public long min;
71      public long max;
72      public int num_hosts;
73      public String [] hostnames;
74      public HeatmapData() {
75      }
76    }
77  
78    private static Log log = LogFactory.getLog(Heatmap.class);
79  
80    static final String START_FIELD_NAME = "start_time_num";
81    static final String END_FIELD_NAME = "finish_time_num";
82  
83    int BOXWIDTH = 250;
84    int SIZE_X = 1600, SIZE_Y=1600;
85    final int [] BORDER = {200,150,150,150};
86    final int LEGEND_X_OFFSET = 10;
87    final int LEGEND_Y_OFFSET = 0;
88    final int LEGEND_TEXT_OFFSET = 10;
89    final int LEGEND_FONT_SIZE = 24;
90    final int AXIS_NAME_FONT_SIZE = 24;
91  
92    protected boolean offline_use = true;
93    protected HttpServletRequest request;
94  
95    // for offline use only
96    // keys that need to be filled:
97    // period (last1/2/3/6/12/24hr,last7d,last30d), time_type (range/last), start, end
98    protected HashMap<String, String> param_map;
99    
100   protected String cluster;
101   protected String timezone;
102   protected String query_state;
103   protected String query_stat_type;
104   protected final String table = new String("filesystem_fsm");
105   protected boolean plot_legend = false; // controls whether to plot hostnames
106   protected boolean sort_nodes = true;
107   protected boolean plot_additional_info = true;
108   protected String add_info_extra = null;
109   
110   protected Display dis;
111   protected Visualization viz;
112   
113   protected Rectangle2D dataBound = new Rectangle2D.Double();
114   protected Rectangle2D xlabBound = new Rectangle2D.Double();
115   protected Rectangle2D ylabBound = new Rectangle2D.Double();
116   protected Rectangle2D labelBottomBound = new Rectangle2D.Double();
117   
118   protected HashMap<String, String> prettyStateNames;
119   
120   /* Different group names allow control of what Renderers to use */
121   final String maingroup = "Data";
122   final String othergroup = "Misc";
123   final String labelgroup = "Label";
124   final String legendgroup = "Legend";
125   final String legendshapegroup = "LegendShape";
126   final String addinfogroup = "AddInfo";
127   final String addinfoshapegroup = "AddInfoShape";
128   
129   public Heatmap() {
130     this.cluster = new String("");
131     this.timezone = new String("");
132     this.query_state = new String("");
133     this.query_stat_type = new String("");
134     param_map = new HashMap<String, String>();    
135   }
136   
137   /**
138    * @brief Constructor for Swimlanes visualization object
139    * @param timezone Timezone string from environment
140    * @param cluster Cluster name from environment
141    * @param event_type Whether to display shuffles or not
142    * @param valmap HashMap of key/value pairs simulating parameters from a HttpRequest
143    */
144   public Heatmap
145     (String timezone, String cluster, String event_type, 
146      String query_stat_type,
147      HashMap<String, String> valmap) 
148   {
149     this.cluster = new String(cluster);
150     if (timezone != null) {
151       this.timezone = new String(timezone);
152     } else {
153       this.timezone = null;
154     }    
155     this.query_state = new String(event_type);
156     this.query_stat_type = new String(query_stat_type);
157 
158     /* This should "simulate" an HttpServletRequest
159      * Need to have "start" and "end" in seconds since Epoch
160      */
161     this.param_map = valmap; 
162   }
163   
164   public Heatmap
165     (String timezone, String cluster, String query_state, 
166      String query_stat_type,
167      HashMap<String, String> valmap, String shuffles) 
168   {
169     
170     this.cluster = new String(cluster);
171     if (timezone != null) {
172       this.timezone = new String(timezone);
173     } else {
174       this.timezone = null;
175     }
176     this.query_state = new String(query_state);
177     this.query_stat_type = new String(query_stat_type);
178 
179     /* This should "simulate" an HttpServletRequest
180      * Need to have "start" and "end" in seconds since Epoch
181      */
182     this.param_map = valmap; 
183     
184   }
185   
186   public Heatmap
187     (String timezone, String cluster, String query_state, 
188      String query_stat_type,
189      HashMap<String, String> valmap, 
190      int w, int h) 
191   {
192     
193     this.cluster = new String(cluster);
194     if (timezone != null) {
195       this.timezone = new String(timezone);
196     } else {
197       this.timezone = null;
198     }
199     this.query_state = new String(query_state);
200     this.query_stat_type = new String(query_stat_type);
201 
202     /* This should "simulate" an HttpServletRequest
203      * Need to have "start" and "end" in seconds since Epoch
204      */
205     this.param_map = valmap; 
206         
207     this.SIZE_X = w;
208     this.SIZE_Y = h;
209   }
210   
211   public Heatmap(HttpServletRequest request) {
212     XssFilter xf = new XssFilter(request);
213     this.offline_use = false;
214     this.request = request;
215     HttpSession session = request.getSession();
216     this.cluster = session.getAttribute("cluster").toString();
217     String query_state = xf.getParameter("query_state");
218     if (query_state != null) {
219       this.query_state = new String(query_state);
220     } else {
221       this.query_state = new String("read");
222     }
223     String query_stat_type = xf.getParameter("query_stat_type");
224     if (query_stat_type != null) {
225       this.query_stat_type = new String(query_stat_type);
226     } else {
227       this.query_stat_type = new String("transaction_count");
228     }
229     this.timezone = session.getAttribute("time_zone").toString();
230   }
231 
232   /**
233    * Set dimensions of image to be generated
234    * Call before calling @see #run
235    */
236   public void setDimensions(int width, int height) {
237     this.SIZE_X=width;
238     this.SIZE_Y=height;
239   }
240   
241   /**
242    * Specify whether to print labels of hosts along axes
243    * Call before calling @see #run
244    */
245   public void setLegend(boolean legendopt) {
246     if (legendopt) {
247       this.plot_legend = true;
248     } else {
249       this.plot_legend = false;
250     }
251   }
252   
253   
254   /**
255    * Generates image in specified format, and writes image as binary
256    * output to supplied output stream 
257    */
258   public boolean getImage(java.io.OutputStream output, String img_fmt, double scale) {
259     dis = new Display(this.viz);
260     dis.setSize(SIZE_X,SIZE_Y);
261     dis.setHighQuality(true);
262     dis.setFont(new Font(Font.SANS_SERIF,Font.PLAIN,24));
263     return dis.saveImage(output, img_fmt, scale);
264   } 
265   
266   protected void setupRenderer() {
267     this.viz.setRendererFactory(new RendererFactory(){
268       AbstractShapeRenderer sr = new ShapeRenderer();
269       ShapeRenderer sr_big = new ShapeRenderer(BOXWIDTH);
270       Renderer arY = new AxisRenderer(Constants.LEFT, Constants.TOP);
271       Renderer arX = new AxisRenderer(Constants.CENTER, Constants.BOTTOM);
272       PolygonRenderer pr = new PolygonRenderer(Constants.POLY_TYPE_LINE);
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/2);
372     legend_labels_table_viz.setFloat(0, VisualItem.Y, BORDER[1]/2);
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/2);
377     legend_labels_table_viz.setFloat(1, VisualItem.Y, BORDER[1] + (BOXWIDTH*hd.num_hosts) + BORDER[3]/2);
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]/2);
382     legend_labels_table_viz.setFloat(2, VisualItem.Y, this.SIZE_Y/2);
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    */
538   public HeatmapData getData() {
539     // preliminary setup
540     OfflineTimeHandler time_offline;
541     TimeHandler time_online;
542     long start, end, min, max;
543     
544     if (offline_use) {
545       time_offline = new OfflineTimeHandler(param_map, this.timezone);
546       start = time_offline.getStartTime();
547       end = time_offline.getEndTime();
548     } else {
549       time_online = new TimeHandler(this.request, this.timezone);
550       start = time_online.getStartTime();
551       end = time_online.getEndTime();
552     }
553     
554     DatabaseWriter dbw = new DatabaseWriter(this.cluster);
555     
556     // setup query
557     String query;
558     if (this.query_state != null && this.query_state.equals("read")) {
559       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')";
560     } else if (this.query_state != null && this.query_state.equals("write")) {
561       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')";
562     } else {
563       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 + "'";
564     } 
565     Macro mp = new Macro(start,end,query);
566     query = mp.toString() + " order by start_time";
567     
568     ArrayList<HashMap<String, Object>> events = new ArrayList<HashMap<String, Object>>();
569 
570     ResultSet rs = null;
571     
572     log.debug("Query: " + query);
573     // run query, extract results
574     try {
575       rs = dbw.query(query);
576       ResultSetMetaData rmeta = rs.getMetaData();
577       int col = rmeta.getColumnCount();
578       while (rs.next()) {
579         HashMap<String, Object> event = new HashMap<String, Object>();
580         for(int i=1;i<=col;i++) {
581           if(rmeta.getColumnType(i)==java.sql.Types.TIMESTAMP) {
582             event.put(rmeta.getColumnName(i),rs.getTimestamp(i).getTime());
583           } else {
584             event.put(rmeta.getColumnName(i),rs.getString(i));
585           }
586         }
587         events.add(event);
588       }
589     } catch (SQLException ex) {
590       // handle any errors
591       log.error("SQLException: " + ex.getMessage());
592       log.error("SQLState: " + ex.getSQLState());
593       log.error("VendorError: " + ex.getErrorCode());
594     } finally {
595       dbw.close();
596     }    
597 
598     log.info(events.size() + " results returned.");
599 
600     HashSet<String> host_set = new HashSet<String>();
601     HashMap<String, Integer> host_indices = new HashMap<String, Integer>();
602     HashMap<Integer, String> host_rev_indices = new HashMap<Integer, String>();
603 
604     // collect hosts, name unique hosts
605     for(int i = 0; i < events.size(); i++) {
606       HashMap<String, Object> event = events.get(i);
607       String curr_host = (String) event.get("hostname");
608       String other_host = (String) event.get("other_host");
609       host_set.add(curr_host);
610       host_set.add(other_host);
611     }
612     int num_hosts = host_set.size();
613 
614     Iterator<String> host_iter = host_set.iterator();
615     for (int i = 0; i < num_hosts && host_iter.hasNext(); i++) {
616       String curr_host = host_iter.next();
617       host_indices.put(curr_host, new Integer(i));
618       host_rev_indices.put(new Integer(i),curr_host);
619     }
620 
621     System.out.println("Number of hosts: " + num_hosts);
622     long stats[][] = new long[num_hosts][num_hosts];
623     long count[][] = new long[num_hosts][num_hosts]; // used for averaging
624 
625     int start_millis = 0, end_millis = 0;
626 
627     // deliberate design choice to duplicate code PER possible operation
628     // otherwise we have to do the mode check N times, for N states returned
629     //
630     // compute aggregate statistics
631     log.info("Query statistic type: "+this.query_stat_type);
632     if (this.query_stat_type.equals("transaction_count")) {
633       for(int i=0;i<events.size();i++) {
634         HashMap<String, Object> event = events.get(i);
635         start=(Long)event.get("start_time");
636         end=(Long)event.get("finish_time");
637         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
638         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
639         String this_host = (String) event.get("hostname");
640         String other_host = (String) event.get("other_host");
641         int this_host_idx = host_indices.get(this_host).intValue();
642         int other_host_idx = host_indices.get(other_host).intValue();
643 
644         // to, from
645         stats[other_host_idx][this_host_idx] += 1;
646       }
647     } else if (this.query_stat_type.equals("avg_duration")) {
648       for(int i=0;i<events.size();i++) {
649         HashMap<String, Object> event = events.get(i);
650         start=(Long)event.get("start_time");
651         end=(Long)event.get("finish_time");
652         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
653         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
654         String this_host = (String) event.get("hostname");
655         String other_host = (String) event.get("other_host");
656         int this_host_idx = host_indices.get(this_host).intValue();
657         int other_host_idx = host_indices.get(other_host).intValue();
658 
659         long curr_val = end_millis - start_millis + ((end - start)*1000);
660 
661         // to, from
662         stats[other_host_idx][this_host_idx] += curr_val;
663         count[other_host_idx][this_host_idx] += 1;
664       }    
665       for (int i = 0; i < num_hosts; i++) {
666         for (int j = 0; j < num_hosts; j++) {
667           if (count[i][j] > 0) stats[i][j] = stats[i][j] / count[i][j];
668         }
669       }
670     } else if (this.query_stat_type.equals("avg_volume")) {
671       for(int i=0;i<events.size();i++) {
672         HashMap<String, Object> event = events.get(i);
673         start=(Long)event.get("start_time");
674         end=(Long)event.get("finish_time");
675         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
676         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
677         String this_host = (String) event.get("hostname");
678         String other_host = (String) event.get("other_host");
679         int this_host_idx = host_indices.get(this_host).intValue();
680         int other_host_idx = host_indices.get(other_host).intValue();
681 
682         long curr_val = Long.parseLong((String)event.get("bytes"));
683 
684         // to, from
685         stats[other_host_idx][this_host_idx] += curr_val;
686         count[other_host_idx][this_host_idx] += 1;
687       }    
688       for (int i = 0; i < num_hosts; i++) {
689         for (int j = 0; j < num_hosts; j++) {
690           if (count[i][j] > 0) stats[i][j] = stats[i][j] / count[i][j];
691         }
692       }
693     } else if (this.query_stat_type.equals("total_duration")) {
694       for(int i=0;i<events.size();i++) {
695         HashMap<String, Object> event = events.get(i);
696         start=(Long)event.get("start_time");
697         end=(Long)event.get("finish_time");
698         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
699         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
700         String this_host = (String) event.get("hostname");
701         String other_host = (String) event.get("other_host");
702         int this_host_idx = host_indices.get(this_host).intValue();
703         int other_host_idx = host_indices.get(other_host).intValue();
704 
705         double curr_val = end_millis - start_millis + ((end - start)*1000);
706 
707         // to, from
708         stats[other_host_idx][this_host_idx] += curr_val;
709       } 
710     } else if (this.query_stat_type.equals("total_volume")) {
711       for(int i=0;i<events.size();i++) {
712         HashMap<String, Object> event = events.get(i);
713         start=(Long)event.get("start_time");
714         end=(Long)event.get("finish_time");
715         start_millis = Integer.parseInt(((String)event.get("start_time_millis")));
716         end_millis = Integer.parseInt(((String)event.get("finish_time_millis")));      
717         String this_host = (String) event.get("hostname");
718         String other_host = (String) event.get("other_host");
719         int this_host_idx = host_indices.get(this_host).intValue();
720         int other_host_idx = host_indices.get(other_host).intValue();
721 
722         long curr_val = Long.parseLong((String)event.get("bytes"));
723 
724         // to, from
725         stats[other_host_idx][this_host_idx] += curr_val;
726       }    
727     }
728     
729     int [] permute = null;
730     if (sort_nodes) {
731       permute = hClust(stats);
732       stats = doPermute(stats,permute);
733     }
734     
735     Table agg_tab = new Table();
736     agg_tab.addColumn("stat", long.class);
737     min = Long.MAX_VALUE;
738     max = Long.MIN_VALUE;
739     agg_tab.addRows(num_hosts*num_hosts);
740     
741     // row-wise placement (row1, followed by row2, etc.)
742     for (int i = 0; i < num_hosts; i++) {
743       for (int j = 0; j < num_hosts; j++) {
744         agg_tab.setLong((i*num_hosts)+j,"stat",stats[i][j]);
745         if (stats[i][j] > max) max = stats[i][j];
746         if (stats[i][j] > 0 && stats[i][j] < min) min = stats[i][j];
747       }
748     }
749     if (min == Long.MAX_VALUE) min = 0;
750     
751     log.info(agg_tab);
752     
753     // collate data
754     HeatmapData hd = new HeatmapData();
755     hd.stats = new long[num_hosts][num_hosts];
756     hd.stats = stats;
757     hd.min = min;
758     hd.max = max;
759     hd.num_hosts = num_hosts;
760     hd.agg_tab = agg_tab;
761     
762     this.add_info_extra = new String("\nState: "+this.prettyStateNames.get(this.query_state)+
763       " ("+events.size()+" "+this.query_state+"'s ["+this.query_stat_type+"])\n" + 
764       "Plotted value range: ["+hd.min+","+hd.max+"] (Zeros in black)");
765 
766     hd.hostnames = new String [num_hosts];
767     for (int i = 0; i < num_hosts; i++) {
768       String curr_host = host_rev_indices.get(new Integer(permute[i]));
769       if (sort_nodes) {
770         hd.hostnames[i] = new String(curr_host);
771       } else {
772         hd.hostnames[i] = new String(curr_host);
773       }
774     }
775     
776     return hd;
777   }
778   
779 }