/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.chukwa.analysis.salsa.visualization; import prefuse.data.io.sql.*; import prefuse.data.Table; import prefuse.data.expression.parser.*; import prefuse.data.expression.*; import prefuse.data.column.*; import prefuse.data.query.*; import prefuse.data.*; import prefuse.action.*; import prefuse.action.layout.*; import prefuse.action.assignment.*; import prefuse.visual.expression.*; import prefuse.visual.*; import prefuse.render.*; import prefuse.util.collections.*; import prefuse.util.*; import prefuse.*; import org.apache.hadoop.chukwa.hicc.OfflineTimeHandler; import org.apache.hadoop.chukwa.hicc.TimeHandler; import org.apache.hadoop.chukwa.util.DatabaseWriter; import org.apache.hadoop.chukwa.database.Macro; import org.apache.hadoop.chukwa.util.XssFilter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.servlet.http.*; import javax.swing.BorderFactory; import java.sql.*; import java.util.*; import java.text.NumberFormat; import java.text.DateFormat; import java.awt.Font; import java.awt.geom.Rectangle2D; /** * Static image generation for Swimlanes visualization for scalable * rendering on front-end client (web-browser) * Handles database data retrieval, transforming data to form for * visualization elements, and initializing and calling visualization * elements */ public class Swimlanes { private static Log log = LogFactory.getLog(Swimlanes.class); int SIZE_X=1600, SIZE_Y=1600; final int [] BORDER = {50,50,50,50}; final int LEGEND_X_OFFSET = 50; final int LEGEND_Y_OFFSET = 25; final int LEGEND_TEXT_OFFSET = 20; final int LEGEND_FONT_SIZE = 18; final int AXIS_NAME_FONT_SIZE = 24; protected boolean offline_use = true; protected HttpServletRequest request; protected String abc; /** * Modifier for generic Swimlanes plots to plot shuffle, sort, and reducer * states of same reduce on same line */ protected class MapReduceSwimlanes { protected Table plot_tab; protected HashMap<String, ArrayList<Tuple> > reducepart_hash; protected boolean collate_reduces = false; public MapReduceSwimlanes() { this.plot_tab = new Table(); this.plot_tab.addColumn("ycoord",float.class); this.plot_tab.addColumn("state_name",String.class); this.plot_tab.addColumn("hostname",String.class); this.plot_tab.addColumn("friendly_id",String.class); this.plot_tab.addColumn(START_FIELD_NAME,double.class); this.plot_tab.addColumn(END_FIELD_NAME,double.class); this.plot_tab.addColumn(PolygonRenderer.POLYGON,float[].class); this.reducepart_hash = new HashMap<String, ArrayList<Tuple> >(); } public void populateTable_OneLinePerState(Table orig_tab) { IntIterator rownumiter; int newrownum, origrownum; rownumiter = orig_tab.rows(); // iterate over everything while (rownumiter.hasNext()) { origrownum = ((Integer)rownumiter.next()).intValue(); newrownum = this.plot_tab.addRow(); this.plot_tab.set(newrownum, "state_name", orig_tab.getString(origrownum, "state_name")); this.plot_tab.set(newrownum, "ycoord", orig_tab.getInt(origrownum, "seqno")); this.plot_tab.set(newrownum,"hostname",orig_tab.getString(origrownum,"hostname")); this.plot_tab.set(newrownum,"friendly_id",orig_tab.getString(origrownum,"friendly_id")); this.plot_tab.set(newrownum,START_FIELD_NAME, orig_tab.getDouble(origrownum,START_FIELD_NAME)); this.plot_tab.set(newrownum,END_FIELD_NAME, orig_tab.getDouble(origrownum,END_FIELD_NAME)); } } public void populateTable_CollateReduces(Table orig_tab) { IntIterator rownumiter; int newrownum, origrownum; this.collate_reduces = true; // add maps normally rownumiter = orig_tab.rows( (Predicate) ExpressionParser.parse("[state_name] == 'map' " + "OR [state_name] == 'shuffle_local' " + "OR [state_name] == 'shuffle_remote'") ); while (rownumiter.hasNext()) { origrownum = ((Integer)rownumiter.next()).intValue(); newrownum = this.plot_tab.addRow(); this.plot_tab.set(newrownum, "state_name", orig_tab.getString(origrownum, "state_name")); this.plot_tab.set(newrownum, "ycoord", orig_tab.getInt(origrownum, "seqno")); this.plot_tab.set(newrownum,"hostname",orig_tab.getString(origrownum,"hostname")); this.plot_tab.set(newrownum,"friendly_id",orig_tab.getString(origrownum,"friendly_id")); this.plot_tab.set(newrownum,START_FIELD_NAME, orig_tab.getDouble(origrownum,START_FIELD_NAME)); this.plot_tab.set(newrownum,END_FIELD_NAME, orig_tab.getDouble(origrownum,END_FIELD_NAME)); } // special breakdown for reduces IntIterator rownumiter3 = orig_tab.rows( (Predicate) ExpressionParser.parse("[state_name] == 'reduce_reducer' " + "OR [state_name] == 'reduce_shufflewait' " + "OR [state_name] == 'reduce_sort' " + "OR [state_name] == 'reduce'") ); ArrayList<Tuple> tuple_array; while (rownumiter3.hasNext()) { origrownum = ((Integer)rownumiter3.next()).intValue(); if (orig_tab.getString(origrownum,"state_name").equals("reduce")) { continue; // do NOT add reduces } String curr_reduce = orig_tab.getString(origrownum, "friendly_id"); newrownum = this.plot_tab.addRow(); this.plot_tab.set(newrownum, "state_name", orig_tab.getString(origrownum, "state_name")); this.plot_tab.set(newrownum, "ycoord", orig_tab.getInt(origrownum, "seqno")); this.plot_tab.set(newrownum,"hostname",orig_tab.getString(origrownum,"hostname")); this.plot_tab.set(newrownum,"friendly_id",orig_tab.getString(origrownum,"friendly_id")); this.plot_tab.set(newrownum,START_FIELD_NAME, orig_tab.getDouble(origrownum,START_FIELD_NAME)); this.plot_tab.set(newrownum,END_FIELD_NAME, orig_tab.getDouble(origrownum,END_FIELD_NAME)); tuple_array = this.reducepart_hash.get(curr_reduce); if (tuple_array == null) { tuple_array = new ArrayList<Tuple>(); tuple_array.add(this.plot_tab.getTuple(newrownum)); this.reducepart_hash.put(curr_reduce, tuple_array); } else { tuple_array.add(this.plot_tab.getTuple(newrownum)); } } } public void populateTable_MapsReducesOnly(Table orig_tab) { IntIterator rownumiter; int newrownum, origrownum; rownumiter = orig_tab.rows( (Predicate) ExpressionParser.parse("[state_name] == 'map' OR [state_name] == 'reduce'") ); while (rownumiter.hasNext()) { origrownum = ((Integer)rownumiter.next()).intValue(); newrownum = this.plot_tab.addRow(); this.plot_tab.set(newrownum, "state_name", orig_tab.getString(origrownum, "state_name")); this.plot_tab.set(newrownum, "ycoord", orig_tab.getInt(origrownum, "seqno")); this.plot_tab.set(newrownum,"hostname",orig_tab.getString(origrownum,"hostname")); this.plot_tab.set(newrownum,"friendly_id",orig_tab.getString(origrownum,"friendly_id")); this.plot_tab.set(newrownum,START_FIELD_NAME, orig_tab.getDouble(origrownum,START_FIELD_NAME)); this.plot_tab.set(newrownum,START_FIELD_NAME, orig_tab.getDouble(origrownum,END_FIELD_NAME)); } } /** * Reassigns Y coord values to group by state */ public void groupByState() { int counter, rownum; int rowcount = this.plot_tab.getRowCount(); HashSet<String> states = new HashSet<String>(); String curr_state = null; Iterator<String> state_iter; IntIterator rownumiter; for (int i = 0; i < rowcount; i++) { states.add(this.plot_tab.getString(i,"state_name")); } state_iter = states.iterator(); counter = 1; while (state_iter.hasNext()) { curr_state = state_iter.next(); if (this.collate_reduces) { if (curr_state.equals("reduce_reducer") || curr_state.equals("reduce_sort")) { continue; } } rownumiter = this.plot_tab.rows( (Predicate) ExpressionParser.parse("[state_name] == '"+curr_state+"'") ); if (this.collate_reduces && curr_state.equals("reduce_shufflewait")) { while (rownumiter.hasNext()) { rownum = ((Integer)rownumiter.next()).intValue(); this.plot_tab.setFloat(rownum,"ycoord",(float)counter); ArrayList<Tuple> alt = this.reducepart_hash.get(this.plot_tab.getString(rownum,"friendly_id")); Object [] tarr = alt.toArray(); for (int i = 0; i < tarr.length; i++) ((Tuple)tarr[i]).setFloat("ycoord",(float)counter); counter++; } } else { while (rownumiter.hasNext()) { rownum = ((Integer)rownumiter.next()).intValue(); this.plot_tab.setFloat(rownum,"ycoord",(float)counter); counter++; } } } } public void groupByStartTime() { int counter, rownum; int rowcount = this.plot_tab.getRowCount(); HashSet<String> states = new HashSet<String>(); String curr_state = null; Iterator<String> state_iter; IntIterator rownumiter; rownumiter = this.plot_tab.rowsSortedBy(START_FIELD_NAME, true); counter = 1; while (rownumiter.hasNext()) { rownum = ((Integer)rownumiter.next()).intValue(); curr_state = this.plot_tab.getString(rownum, "state_name"); if (this.collate_reduces && curr_state.equals("reduce_shufflewait")) { this.plot_tab.setFloat(rownum,"ycoord",(float)counter); ArrayList<Tuple> alt = this.reducepart_hash.get(this.plot_tab.getString(rownum,"friendly_id")); Object [] tarr = alt.toArray(); for (int i = 0; i < tarr.length; i++) ((Tuple)tarr[i]).setFloat("ycoord",(float)counter); counter++; } else if (!curr_state.equals("reduce_sort") && !curr_state.equals("reduce_reducer")) { this.plot_tab.setFloat(rownum,"ycoord",(float)counter); counter++; } } } public void groupByEndTime() { int counter, rownum; int rowcount = this.plot_tab.getRowCount(); HashSet<String> states = new HashSet<String>(); String curr_state = null; Iterator<String> state_iter; IntIterator rownumiter; rownumiter = this.plot_tab.rowsSortedBy(END_FIELD_NAME, true); counter = 1; while (rownumiter.hasNext()) { rownum = ((Integer)rownumiter.next()).intValue(); curr_state = this.plot_tab.getString(rownum, "state_name"); if (this.collate_reduces && curr_state.equals("reduce_reducer")) { this.plot_tab.setFloat(rownum,"ycoord",(float)counter); ArrayList<Tuple> alt = this.reducepart_hash.get(this.plot_tab.getString(rownum,"friendly_id")); Object [] tarr = alt.toArray(); for (int i = 0; i < tarr.length; i++) ((Tuple)tarr[i]).setFloat("ycoord",(float)counter); counter++; } else if (!curr_state.equals("reduce_sort") && !curr_state.equals("reduce_shufflewait")) { this.plot_tab.setFloat(rownum,"ycoord",(float)counter); counter++; } } } public VisualTable addToVisualization(Visualization viz, String groupname) { return viz.addTable(groupname, this.plot_tab); } } /** * Provide constant mapping between state names and colours * so that even if particular states are missing, the colours are fixed * for each state */ public static class SwimlanesStatePalette { protected final String [] states = {"map","reduce","reduce_shufflewait","reduce_sort","reduce_reducer","shuffle"}; HashMap<String,Integer> colourmap; protected int [] palette; public SwimlanesStatePalette() { palette = ColorLib.getCategoryPalette(states.length); colourmap = new HashMap<String,Integer>(); for (int i = 0; i < states.length; i++) { colourmap.put(states[i], new Integer(palette[i])); } } public int getColour(String state_name) { Integer val = colourmap.get(state_name); if (val == null) { return ColorLib.color(java.awt.Color.BLACK); } else { return val.intValue(); } } public int getNumStates() { return this.states.length; } public String [] getStates() { return this.states; } } /** * Provides convenient rescaling of raw values to be plotted to * actual pixels for plotting on image */ public static class CoordScaler { double x_pixel_size, y_pixel_size; double x_max_value, y_max_value, x_min_value, y_min_value; double x_start, y_start; public CoordScaler() { this.x_pixel_size = 0.0; this.y_pixel_size = 0.0; this.x_max_value = 1.0; this.y_max_value = 1.0; this.x_min_value = 0.0; this.y_min_value = 0.0; this.x_start = 0.0; this.y_start = 0.0; } public void set_pixel_start(double x, double y) { this.x_start = x; this.y_start = y; } public void set_pixel_size(double x, double y) { this.x_pixel_size = x; this.y_pixel_size = y; } public void set_value_ranges(double x_min, double y_min, double x_max, double y_max) { this.x_max_value = x_max; this.y_max_value = y_max; this.x_min_value = x_min; this.y_min_value = y_min; } public double get_x_coord(double x_value) { return x_start+(((x_value - x_min_value) / (x_max_value-x_min_value)) * x_pixel_size); } public double get_y_coord(double y_value) { // this does "inverting" to shift the (0,0) point from top-right to bottom-right return y_start+(y_pixel_size - ((((y_value - y_min_value) / (y_max_value-y_min_value)) * y_pixel_size))); } } /** * Prefuse action for plotting a line for each state */ public static class SwimlanesStateAction extends GroupAction { protected CoordScaler cs; public SwimlanesStateAction() { super(); } public SwimlanesStateAction(String group, CoordScaler cs) { super(group); this.cs = cs; } public void run (double frac) { VisualItem item = null; SwimlanesStatePalette pal = new SwimlanesStatePalette(); Iterator curr_group_items = this.m_vis.items(this.m_group); int i = 0; while (curr_group_items.hasNext()) { item = (VisualItem) curr_group_items.next(); double start_time = item.getDouble(START_FIELD_NAME); double finish_time = item.getDouble(END_FIELD_NAME); item.setShape(Constants.POLY_TYPE_LINE); item.setX(0.0); item.setY(0.0); float [] coords = new float[4]; coords[0] = (float) cs.get_x_coord(start_time); coords[1] = (float) cs.get_y_coord((double)item.getInt("ycoord")); coords[2] = (float) cs.get_x_coord(finish_time); coords[3] = (float) cs.get_y_coord((double)item.getInt("ycoord")); item.set(VisualItem.POLYGON,coords); item.setStrokeColor(pal.getColour(item.getString("state_name"))); i++; } } } // SwimlanesStateAction // keys that need to be filled: // period (last1/2/3/6/12/24hr,last7d,last30d), time_type (range/last), start, end protected HashMap<String, String> param_map; protected String cluster; protected String timezone; protected String shuffle_option; protected final String table = new String("mapreduce_fsm"); protected boolean plot_legend = true; protected String jobname = null; protected Display dis; protected Visualization viz; protected Rectangle2D dataBound = new Rectangle2D.Double(); protected Rectangle2D xlabBound = new Rectangle2D.Double(); protected Rectangle2D ylabBound = new Rectangle2D.Double(); protected Rectangle2D labelBottomBound = new Rectangle2D.Double(); static final String START_FIELD_NAME = "start_time_num"; static final String END_FIELD_NAME = "finish_time_num"; /* Different group names allow control of what Renderers to use */ final String maingroup = "Job"; final String othergroup = "Misc"; final String labelgroup = "Label"; final String legendgroup = "Legend"; final String legendshapegroup = "LegendShape"; public Swimlanes() { this.cluster = new String(""); this.timezone = new String(""); this.shuffle_option = new String(""); param_map = new HashMap<String, String>(); } /** * @brief Constructor for Swimlanes visualization object * @param timezone Timezone string from environment * @param cluster Cluster name from environment * @param event_type Whether to display shuffles or not * @param valmap HashMap of key/value pairs simulating parameters from a HttpRequest */ public Swimlanes (String timezone, String cluster, String event_type, HashMap<String, String> valmap) { this.cluster = new String(cluster); if (timezone != null) { this.timezone = new String(timezone); } else { this.timezone = null; } this.shuffle_option = new String(event_type); /* This should "simulate" an HttpServletRequest * Need to have "start" and "end" in seconds since Epoch */ this.param_map = valmap; } public Swimlanes (String timezone, String cluster, String event_type, HashMap<String, String> valmap, int width, int height) { this.cluster = new String(cluster); if (timezone != null) { this.timezone = new String(timezone); } else { this.timezone = null; } this.shuffle_option = new String(event_type); /* This should "simulate" an HttpServletRequest * Need to have "start" and "end" in seconds since Epoch */ this.param_map = valmap; this.SIZE_X = width; this.SIZE_Y = height; } public Swimlanes (String timezone, String cluster, String event_type, HashMap<String, String> valmap, int width, int height, String legend_opt) { this.cluster = new String(cluster); if (timezone != null) { this.timezone = new String(timezone); } else { this.timezone = null; } this.shuffle_option = new String(event_type); /* This should "simulate" an HttpServletRequest * Need to have "start" and "end" in seconds since Epoch */ this.param_map = valmap; this.SIZE_X = width; this.SIZE_Y = height; if (legend_opt.equals("nolegend")) { this.plot_legend = false; } } public Swimlanes(HttpServletRequest request) { XssFilter xf = new XssFilter(request); this.offline_use = false; this.request = request; HttpSession session = request.getSession(); this.cluster = session.getAttribute("cluster").toString(); String evt_type = xf.getParameter("event_type"); if (evt_type != null) { this.shuffle_option = new String(evt_type); } else { this.shuffle_option = new String("noshuffle"); } this.timezone = session.getAttribute("time_zone").toString(); } /** * Set job ID to filter results on * Call before calling @see #run */ public void setJobName(String s) { this.jobname = new String(s); } /** * Set dimensions of image to be generated * Call before calling @see #run */ public void setDimensions(int width, int height) { this.SIZE_X=width; this.SIZE_Y=height; } /** * Specify whether to print legend of states * Advisable to not print legend for excessively small images since * legend has fixed point size * Call before calling @see #run */ public void setLegend(boolean legendopt) { if (legendopt) { this.plot_legend = true; } else { this.plot_legend = false; } } /** * Generates image in specified format, and writes image as binary * output to supplied output stream */ public boolean getImage(java.io.OutputStream output, String img_fmt, double scale) { dis = new Display(this.viz); dis.setSize(SIZE_X,SIZE_Y); dis.setHighQuality(true); dis.setFont(new Font(Font.SANS_SERIF,Font.PLAIN,24)); return dis.saveImage(output, img_fmt, scale); } /** * Adds a column to given table by converting timestamp to long with * seconds since epoch, and adding milliseconds from additional column * in original table * * @param origTable Table to add to * @param srcFieldName Name of column containing timestamp * @param srcMillisecondFieldName Name of column containing millisecond value of time * @param dstFieldName Name of new column to add * * @return Modified table with added column */ protected Table addTimeCol (Table origTable, String srcFieldName, String srcMillisecondFieldName, String dstFieldName) { origTable.addColumn(dstFieldName, long.class); int total_rows = origTable.getRowCount(); for (int curr_row_num = 0; curr_row_num < total_rows; curr_row_num++) { origTable.setLong(curr_row_num, dstFieldName, ((Timestamp)origTable.get(curr_row_num, srcFieldName)).getTime() + origTable.getLong(curr_row_num, srcMillisecondFieldName) ); } return origTable; } /** * Adds a column with number of seconds of timestamp elapsed since lowest * start time; allows times to be plotted as a delta of the start time * * @param origTable Table to add column to * @param srcFieldName Name of column containing timestamp * @param srcMillisecondFieldName Name of column containing millisecond value of time * @param dstFieldName Name of new column to add * * @return Modified table with added column */ protected Table addTimeOffsetCol (Table origTable, String srcFieldName, String srcMillisecondFieldName, String dstFieldName, long timeOffset) { Table newtable = addTimeCol(origTable, srcFieldName, srcMillisecondFieldName, dstFieldName + "_fulltime"); ColumnMetadata dstcol = newtable.getMetadata(dstFieldName + "_fulltime"); long mintime = newtable.getLong(dstcol.getMinimumRow(), dstFieldName + "_fulltime"); if (timeOffset == 0) { newtable.addColumn(dstFieldName, "ROUND((["+dstFieldName+"_fulltime] - " + mintime +"L) / 1000L)"); } else { newtable.addColumn(dstFieldName, "ROUND((["+dstFieldName+"_fulltime] - " + timeOffset +"L) / 1000L)"); } return newtable; } protected void setupRenderer() { this.viz.setRendererFactory(new RendererFactory(){ AbstractShapeRenderer sr = new ShapeRenderer(); ShapeRenderer sr_big = new ShapeRenderer(20); Renderer arY = new AxisRenderer(Constants.LEFT, Constants.TOP); Renderer arX = new AxisRenderer(Constants.CENTER, Constants.BOTTOM); PolygonRenderer pr = new PolygonRenderer(Constants.POLY_TYPE_LINE); LabelRenderer lr = new LabelRenderer("label"); LabelRenderer lr_legend = new LabelRenderer("label"); public Renderer getRenderer(VisualItem item) { lr.setHorizontalAlignment(Constants.CENTER); lr.setVerticalAlignment(Constants.TOP); lr_legend.setHorizontalAlignment(Constants.LEFT); lr_legend.setVerticalAlignment(Constants.CENTER); if (item.isInGroup("ylab")) { return arY; } else if (item.isInGroup("xlab")) { return arX; } else if (item.isInGroup(maingroup)) { return pr; } else if (item.isInGroup(labelgroup)) { return lr; } else if (item.isInGroup(legendgroup)) { return lr_legend; } else if (item.isInGroup(legendshapegroup)) { return sr_big; } else { return sr; } } }); } // setup columns: add additional time fields protected Table setupDataTable() { Table res_tab = this.getData(); if (res_tab == null) { return res_tab; } res_tab.addColumn("seqno","ROW()"); res_tab = addTimeOffsetCol(res_tab, "start_time", "start_time_millis", START_FIELD_NAME, 0); ColumnMetadata dstcol = res_tab.getMetadata(START_FIELD_NAME); long mintime = ((Timestamp)res_tab.get(dstcol.getMinimumRow(), "start_time")).getTime(); res_tab = addTimeOffsetCol(res_tab, "finish_time", "finish_time_millis", END_FIELD_NAME, mintime); res_tab.addColumn(PolygonRenderer.POLYGON,float[].class); log.debug("After adding seqno: #cols: " + res_tab.getColumnCount() + "; #rows: " + res_tab.getRowCount()); return res_tab; } protected void addAxisNames() { Table textlabels_table = new Table(); textlabels_table.addColumn("label",String.class); textlabels_table.addColumn("type",String.class); textlabels_table.addRow(); textlabels_table.setString(0,"label",new String("Time/s")); textlabels_table.setString(0,"type",new String("xaxisname")); VisualTable textlabelsviz = this.viz.addTable(labelgroup, textlabels_table); textlabelsviz.setX(0,SIZE_X/2); textlabelsviz.setY(0,SIZE_Y - BORDER[2] + (BORDER[2]*0.1)); textlabelsviz.setTextColor(0,ColorLib.color(java.awt.Color.GRAY)); textlabelsviz.setFont(0,new Font(Font.SANS_SERIF,Font.PLAIN,AXIS_NAME_FONT_SIZE)); } protected void addLegend() { SwimlanesStatePalette ssp = new SwimlanesStatePalette(); Table shapes_table = new Table(); shapes_table.addColumn(VisualItem.X,float.class); shapes_table.addColumn(VisualItem.Y,float.class); Table legend_labels_table = new Table(); Table legend_squares_table = new Table(); legend_labels_table.addColumn("label",String.class); // add labels int num_states = ssp.getNumStates(); String [] state_names = ssp.getStates(); legend_labels_table.addRows(num_states); shapes_table.addRows(num_states); for (int i = 0; i < num_states; i++) { legend_labels_table.setString(i,"label",state_names[i]); } // add legend shapes, manipulate visualitems to set colours VisualTable shapes_table_viz = viz.addTable(legendshapegroup, shapes_table); float start_x = BORDER[0] + LEGEND_X_OFFSET; float start_y = BORDER[1] + LEGEND_Y_OFFSET; float incr = (float) 30.0; for (int i = 0; i < num_states; i++) { shapes_table_viz.setFillColor(i, ssp.getColour(state_names[i])); shapes_table_viz.setFloat(i, VisualItem.X, start_x); shapes_table_viz.setFloat(i, VisualItem.Y, start_y + (i * incr)); } // add legend labels, manipulate visualitems to set font VisualTable legend_labels_table_viz = this.viz.addTable(legendgroup, legend_labels_table); for (int i = 0; i < num_states; i++) { legend_labels_table_viz.setFloat(i, VisualItem.X, start_x + LEGEND_TEXT_OFFSET); legend_labels_table_viz.setFloat(i, VisualItem.Y, start_y + (i * incr)); legend_labels_table_viz.setTextColor(i,ColorLib.color(java.awt.Color.BLACK)); legend_labels_table_viz.setFont(i,new Font(Font.SANS_SERIF,Font.PLAIN,LEGEND_FONT_SIZE)); } } public void run() { // setup bounds this.dataBound.setRect(BORDER[0],BORDER[1],SIZE_X-BORDER[2]-BORDER[0],SIZE_Y-BORDER[3]-BORDER[1]); this.xlabBound.setRect(BORDER[0],BORDER[1],SIZE_X-BORDER[2]-BORDER[0],SIZE_Y-BORDER[3]-BORDER[1]); this.ylabBound.setRect(BORDER[0],BORDER[1],SIZE_X-BORDER[2]-BORDER[0],SIZE_Y-BORDER[3]-BORDER[1]); this.labelBottomBound.setRect(BORDER[0],SIZE_X-BORDER[2],SIZE_Y-BORDER[0]-BORDER[1],BORDER[3]); // setup visualization this.viz = new Visualization(); this.setupRenderer(); // add table to visualization Table raw_data_tab = this.setupDataTable(); MapReduceSwimlanes mrs = new MapReduceSwimlanes(); mrs.populateTable_CollateReduces(raw_data_tab); mrs.groupByState(); VisualTable maindatatable = mrs.addToVisualization(this.viz, maingroup); addAxisNames(); if (plot_legend) { addLegend(); } // plot swimlanes lines: setup axes, call custom action ActionList draw = new ActionList(); { // setup axes AxisLayout xaxis = new AxisLayout(maingroup, START_FIELD_NAME, Constants.X_AXIS, VisiblePredicate.TRUE); AxisLayout yaxis = new AxisLayout(maingroup, "ycoord", Constants.Y_AXIS, VisiblePredicate.FALSE); xaxis.setLayoutBounds(dataBound); yaxis.setLayoutBounds(dataBound); ColumnMetadata starttime_meta = maindatatable.getMetadata(START_FIELD_NAME); ColumnMetadata finishtime_meta = maindatatable.getMetadata(END_FIELD_NAME); ColumnMetadata ycoord_meta = maindatatable.getMetadata("ycoord"); long x_min = (long) ((Double)maindatatable.get(starttime_meta.getMinimumRow(), START_FIELD_NAME)).doubleValue(); long x_max = (long) ((Double)maindatatable.get(finishtime_meta.getMaximumRow(), END_FIELD_NAME)).doubleValue(); xaxis.setRangeModel(new NumberRangeModel(x_min,x_max,x_min,x_max)); float y_max = maindatatable.getFloat(ycoord_meta.getMaximumRow(),"ycoord"); yaxis.setRangeModel(new NumberRangeModel(0,y_max,0,y_max)); // call custom action to plot actual swimlanes lines CoordScaler cs = new CoordScaler(); cs.set_pixel_size(SIZE_X-BORDER[0]-BORDER[2], SIZE_Y-BORDER[1]-BORDER[3]); cs.set_pixel_start(BORDER[0],BORDER[1]); cs.set_value_ranges(x_min,0,x_max,y_max); //SwimlanesStateAction swimlaneslines = new SwimlanesStateAction(maingroup, cs); SwimlanesStateAction swimlaneslines = new SwimlanesStateAction(maingroup, cs); // add everything to the plot draw.add(xaxis); draw.add(yaxis); draw.add(swimlaneslines); AxisLabelLayout xlabels = new AxisLabelLayout("xlab", xaxis, xlabBound); this.viz.putAction("xlabels",xlabels); AxisLabelLayout ylabels = new AxisLabelLayout("ylab", yaxis, ylabBound); this.viz.putAction("ylabels",ylabels); } // add axes names { SpecifiedLayout sl = new SpecifiedLayout(labelgroup, VisualItem.X, VisualItem.Y); ActionList labeldraw = new ActionList(); labeldraw.add(sl); this.viz.putAction(labelgroup, labeldraw); } // add legend if (plot_legend) { ShapeAction legend_sa = new ShapeAction(legendshapegroup); SpecifiedLayout legendlabels_sl = new SpecifiedLayout(legendgroup, VisualItem.X, VisualItem.Y); ActionList legenddraw = new ActionList(); legenddraw.add(legend_sa); this.viz.putAction(legendshapegroup, legenddraw); ActionList legendlabelsdraw = new ActionList(); legendlabelsdraw.add(legendlabels_sl); this.viz.putAction(legendgroup,legendlabelsdraw); } // draw everything else this.viz.putAction("draw",draw); // finally draw this.viz.run("draw"); this.viz.run("xlabels"); this.viz.run("ylabels"); } public Table getData() { // preliminary setup OfflineTimeHandler time_offline; TimeHandler time_online; long start, end; if (offline_use) { time_offline = new OfflineTimeHandler(param_map, this.timezone); start = time_offline.getStartTime(); end = time_offline.getEndTime(); } else { time_online = new TimeHandler(this.request, this.timezone); start = time_online.getStartTime(); end = time_online.getEndTime(); } DatabaseWriter dbw = new DatabaseWriter(this.cluster); String query; // setup query if (this.shuffle_option != null && this.shuffle_option.equals("shuffles")) { query = "select job_id,friendly_id,start_time,finish_time,start_time_millis,finish_time_millis,status,state_name,hostname from ["+this.table+"] where finish_time between '[start]' and '[end]'"; } else { query = "select job_id,friendly_id,start_time,finish_time,start_time_millis,finish_time_millis,status,state_name,hostname from ["+this.table+"] where finish_time between '[start]' and '[end]' and not state_name like 'shuffle_local' and not state_name like 'shuffle_remote'"; } if (this.jobname != null) { query = query + " and job_id like '"+ this.jobname +"'"; } Macro mp = new Macro(start,end,query); query = mp.toString() + " order by start_time"; Table rs_tab = null; DatabaseDataSource dds; DefaultSQLDataHandler dh = new DefaultSQLDataHandler(); log.debug("Query: " + query); // execute query try { dds = ConnectionFactory.getDatabaseConnection(dbw.getConnection()); rs_tab = dds.getData(query); } catch (prefuse.data.io.DataIOException e) { System.err.println("prefuse data IO error: " + e); log.warn("prefuse data IO error: " + e); return null; } catch (SQLException e) { System.err.println("Error in SQL: " + e + " in statement: " + query); log.warn("Error in SQL: " + e + " in statement: " + query); return null; } HashMap<String, Integer> state_counts = new HashMap<String, Integer>(); HashSet<String> states = new HashSet<String>(); for (int i = 0; i < rs_tab.getRowCount(); i++) { String curr_state = rs_tab.getString(i, "state_name"); states.add(curr_state); Integer cnt = state_counts.get(curr_state); if (cnt == null) { state_counts.put(curr_state, new Integer(1)); } else { state_counts.remove(curr_state); state_counts.put(curr_state, new Integer(cnt.intValue()+1)); } } log.info("Search complete: #cols: " + rs_tab.getColumnCount() + "; #rows: " + rs_tab.getRowCount()); return rs_tab; } }