Home | History | Annotate | Download | only in ui
      1 /*******************************************************************************
      2  * Copyright (c) 2000, 2009 IBM Corporation and others.
      3  * All rights reserved. This program and the accompanying materials
      4  * are made available under the terms of the Eclipse Public License v1.0
      5  * which accompanies this distribution, and is available at
      6  * http://www.eclipse.org/legal/epl-v10.html
      7  *
      8  * Contributors:
      9  *     IBM Corporation - initial API and implementation
     10  *******************************************************************************/
     11 package org.eclipse.test.internal.performance.results.ui;
     12 
     13 import java.util.HashMap;
     14 import java.util.Iterator;
     15 import java.util.List;
     16 import java.util.Map;
     17 
     18 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
     19 import org.eclipse.core.runtime.preferences.InstanceScope;
     20 import org.eclipse.swt.SWT;
     21 import org.eclipse.swt.custom.CTabFolder;
     22 import org.eclipse.swt.events.MouseEvent;
     23 import org.eclipse.swt.events.MouseListener;
     24 import org.eclipse.swt.events.MouseTrackListener;
     25 import org.eclipse.swt.graphics.Color;
     26 import org.eclipse.swt.graphics.Font;
     27 import org.eclipse.swt.graphics.FontData;
     28 import org.eclipse.swt.graphics.GC;
     29 import org.eclipse.swt.graphics.Point;
     30 import org.eclipse.swt.graphics.Rectangle;
     31 import org.eclipse.swt.layout.GridData;
     32 import org.eclipse.swt.widgets.Composite;
     33 import org.eclipse.swt.widgets.Display;
     34 import org.eclipse.swt.widgets.Shell;
     35 import org.eclipse.swt.widgets.Table;
     36 import org.eclipse.swt.widgets.TableColumn;
     37 import org.eclipse.swt.widgets.TableItem;
     38 import org.eclipse.swt.widgets.ToolTip;
     39 import org.eclipse.test.internal.performance.results.db.AbstractResults;
     40 import org.eclipse.test.internal.performance.results.model.BuildResultsElement;
     41 import org.eclipse.test.internal.performance.results.model.ComponentResultsElement;
     42 import org.eclipse.test.internal.performance.results.model.ConfigResultsElement;
     43 import org.eclipse.test.internal.performance.results.model.ResultsElement;
     44 import org.eclipse.test.internal.performance.results.model.ScenarioResultsElement;
     45 import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
     46 import org.eclipse.test.internal.performance.results.utils.Util;
     47 
     48 
     49 /**
     50  * Tab to display all performances results numbers for a configuration (i.e a performance machine).
     51  */
     52 public class ConfigTab {
     53 
     54 	// Colors
     55 	static final Display DEFAULT_DISPLAY = Display.getDefault();
     56 	static final Color BLUE= DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_BLUE);
     57 	static final Color DARK_GREEN= DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_DARK_GREEN);
     58 	static final Color GRAY = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_GRAY);
     59 	static final Color MAGENTA = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_MAGENTA);
     60 	static final Color RED = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_RED);
     61 
     62 	// SWT resources
     63 	Shell shell;
     64 	Display display;
     65 	Table table;
     66 	private GC gc;
     67 	private Color lightred;
     68 	private Color lightyellow;
     69 	private Color darkyellow;
     70 	private Color blueref;
     71 	private Font boldFont;
     72 	private Font italicFont;
     73 	private Font boldItalicFont;
     74 	Map toolTips;
     75 
     76 	// Information
     77 	String configBox, configName;
     78 	ComponentResultsElement results;
     79 	double[][] allValues;
     80 	double[][] allErrors;
     81 
     82 	// Cells management
     83 	Point tableOrigin, tableSize;
     84 	int columnsCount, rowsCount;
     85 	List firstLine;
     86 
     87 	// Eclipse preferences
     88 	private IEclipsePreferences preferences;
     89 
     90 /*
     91  * Default constructor.
     92  */
     93 public ConfigTab(String name, String box) {
     94     this.configName = name;
     95     int idx = box.indexOf(" (");
     96 	this.configBox = idx > 0 ? box.substring(0, idx) : box;
     97 	this.preferences = new InstanceScope().getNode(IPerformancesConstants.PLUGIN_ID);
     98 }
     99 
    100 /**
    101  * Creates the tab folder page.
    102  *
    103  * @param tabFolder org.eclipse.swt.widgets.TabFolder
    104  * @param fullSelection Tells whether the table should have a full line selection or not
    105  * @return the new page for the tab folder
    106  */
    107 Composite createTabFolderPage (ComponentResultsElement componentResultsElement, CTabFolder tabFolder, boolean fullSelection) {
    108 	// Cache the shell and display.
    109 	this.shell = tabFolder.getShell();
    110 	this.display = this.shell.getDisplay();
    111 
    112 	// Remove old table is present
    113 	boolean initResources = this.table == null;
    114 	if (this.table != null) {
    115 		disposeTable();
    116 	}
    117 
    118 	// Store results
    119 	this.results = componentResultsElement;
    120 
    121 	// Create the "children" table
    122 	int style = SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL;
    123 	if (fullSelection) style |= SWT.FULL_SELECTION;
    124 	this.table = new Table(tabFolder, style);
    125 	this.table.setLinesVisible (true);
    126 	this.table.setHeaderVisible (true);
    127 	GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1);
    128 	gridData.heightHint = 150;
    129 	this.table.setLayoutData (gridData);
    130 	this.gc = new GC(this.table);
    131 
    132 	// Init resources
    133 	if (initResources) initResources();
    134 
    135 	// Add columns to the table
    136 	boolean fingerprints = this.preferences.getBoolean(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS, IPerformancesConstants.DEFAULT_FILTER_ADVANCED_SCENARIOS);
    137 	String [] columnHeaders = getLayoutDataFieldNames(fingerprints);
    138 	int length = columnHeaders.length;
    139 	for (int i = 0; i < length; i++) {
    140 		TableColumn column = new TableColumn(this.table, SWT.CENTER);
    141 		column.setText (columnHeaders [i]);
    142 	}
    143 
    144 	// Add lines to the table
    145 	this.toolTips = new HashMap();
    146 	fillTableLines(fingerprints);
    147 
    148 	// Updated columns
    149 	for (int i=0; i<length; i++) {
    150 		TableColumn column = this.table.getColumn(i);
    151 		column.setWidth(i==0?120:100);
    152 		if (i > 0) {
    153 			String text = (String) this.firstLine.get(i);
    154 			column.setToolTipText(text);
    155 		}
    156 	}
    157 
    158 	// Store table info
    159 	this.columnsCount = length;
    160 
    161 	// Listen to mouse events to select the corresponding build in the components view
    162 	// when a click is done in the table cell.
    163 	final ComponentsView componentsView = (ComponentsView) PerformancesView.getWorkbenchView("org.eclipse.test.internal.performance.results.ui.ComponentsView");
    164 	MouseListener mouseListener = new MouseListener() {
    165 		public void mouseUp(MouseEvent e) {
    166 		}
    167 		public void mouseDown(MouseEvent e) {
    168 			Point cellPosition = currentCellPosition(e.x, e.y);
    169 			Table tabTable = ConfigTab.this.table;
    170 			componentsView.select(ConfigTab.this.results, ConfigTab.this.configName, (String) ConfigTab.this.firstLine.get(cellPosition.x), tabTable.getItem(cellPosition.y).getText());
    171 		}
    172 		public void mouseDoubleClick(MouseEvent e) {
    173 		}
    174 	};
    175 	this.table.addMouseListener(mouseListener);
    176 
    177 	// Listen to mouse track events to display the table cell corresponding tooltip.
    178 	MouseTrackListener mouseTrackListener = new MouseTrackListener() {
    179 		ToolTip currentTooltip;
    180 		public void mouseHover(MouseEvent e) {
    181 			if (this.currentTooltip != null) {
    182 				this.currentTooltip.setVisible(false);
    183 				this.currentTooltip = null;
    184 			}
    185 			Point cellPosition = currentCellPosition(e.x, e.y);
    186 			if (cellPosition != null) {
    187 				ToolTip tooltip = (ToolTip) ConfigTab.this.toolTips.get(cellPosition);
    188 				if (tooltip != null) {
    189 					Point location = ConfigTab.this.table.toDisplay(new Point(e.x, e.y));
    190 					tooltip.setLocation(location);
    191 					tooltip.setVisible(true);
    192 					this.currentTooltip = tooltip;
    193 				}
    194 			}
    195 		}
    196 		public void mouseEnter(MouseEvent e) {
    197 		}
    198 		public void mouseExit(MouseEvent e) {
    199 		}
    200 	};
    201 	this.table.addMouseTrackListener(mouseTrackListener);
    202 
    203 	// Select the first line by default (as this is the current build)
    204 	this.table.select(0);
    205 
    206 	// Return the built composite
    207 	return this.table;
    208 }
    209 
    210 /*
    211  * Create and store a tooltip with the given information and at the given position.
    212  */
    213 void createToolTip(String toolTipText, String toolTipMessage, int toolTipStyle, Point position) {
    214 	ToolTip toolTip = new ToolTip(this.table.getShell(), toolTipStyle);
    215 	toolTip.setAutoHide(true);
    216 	toolTip.setText(toolTipText);
    217 	toolTip.setMessage(/*"("+col+","+row+") "+*/toolTipMessage);
    218 	this.toolTips.put(position, toolTip);
    219 }
    220 
    221 /*
    222  * Get the current cell position (column, row) from a point position.
    223  */
    224 Point currentCellPosition(int x, int y) {
    225 
    226 	// Compute the origin of the visible area
    227 	if (this.tableOrigin == null) {
    228 		this.tableOrigin = new Point(0, this.table.getHeaderHeight());
    229 	}
    230 
    231 	// Increment width until over current y position
    232 	int height= this.tableOrigin.y;
    233 	int row = this.table.getTopIndex();
    234 	while (row<this.rowsCount && height<y) {
    235 		height += this.table.getItemHeight();
    236 		row++;
    237 	}
    238 	if (height < y) {
    239 		// return when position is over the last line
    240 		return null;
    241 	}
    242 	row--;
    243 
    244 	// Increment width until being over current x position
    245 	int col = 0;
    246 	TableItem tableItem = this.table.getItem(row);
    247 	Rectangle bounds = tableItem.getBounds(col);
    248 	while (col<this.columnsCount) {
    249 		int max = bounds.x + bounds.width + this.table.getGridLineWidth();
    250 		if (x <= max) break;
    251 		if (col == this.columnsCount) {
    252 			// return when position is over the last column
    253 			return null;
    254 		}
    255 		col++;
    256 		bounds = tableItem.getBounds(col);
    257 	}
    258 
    259 	// Return the found table cell position
    260 	return new Point(col, row);
    261 }
    262 
    263 /*
    264  * Dispose all SWT resources.
    265  */
    266 public void dispose() {
    267 	if (this.boldFont != null) {
    268 		this.boldFont.dispose();
    269 	}
    270 	if (this.italicFont != null) {
    271 		this.italicFont.dispose();
    272 	}
    273 	if (this.boldItalicFont != null) {
    274 		this.boldItalicFont.dispose();
    275 	}
    276 	if (this.darkyellow != null) {
    277 		this.darkyellow.dispose();
    278 	}
    279 	if (this.lightyellow != null) {
    280 		this.lightyellow.dispose();
    281 	}
    282 	if (this.lightred != null) {
    283 		this.lightred.dispose();
    284 	}
    285 	if (this.blueref != null) {
    286 		this.blueref.dispose();
    287 	}
    288 	disposeTable();
    289 }
    290 
    291 /*
    292  * Dispose all SWT controls associated with the table.
    293  */
    294 private void disposeTable() {
    295 	if (this.toolTips != null) {
    296 		Iterator cells = this.toolTips.keySet().iterator();
    297 		while (cells.hasNext()) {
    298 			ToolTip toolTip = (ToolTip) this.toolTips.get(cells.next());
    299 			toolTip.dispose();
    300 		}
    301 	}
    302 	this.table.dispose();
    303 	this.tableOrigin = null;
    304 	this.firstLine = null;
    305 }
    306 
    307 /*
    308  * Fill the lines of the tables.
    309  * Get all the information from the model which are returned in a list (lines) of lists (columns).
    310  */
    311 private void fillTableLines(boolean fingerprints) {
    312 
    313 	// Get preferences information
    314 	boolean onlyMilestones = this.preferences.getBoolean(IPerformancesConstants.PRE_FILTER_OLD_BUILDS, IPerformancesConstants.DEFAULT_FILTER_OLD_BUILDS);
    315 	boolean skipNightlyBuilds = this.preferences.getBoolean(IPerformancesConstants.PRE_FILTER_NIGHTLY_BUILDS, IPerformancesConstants.DEFAULT_FILTER_NIGHTLY_BUILDS);
    316 
    317 	// Get model information
    318 	if (this.results == null) return;
    319 	List differences = this.results.getConfigNumbers(this.configName, fingerprints);
    320 	if (differences == null) return;
    321 
    322 	// Store first information line which are the scenarios full names
    323 	Iterator lines = differences.iterator();
    324 	this.firstLine = (List) lines.next();
    325 
    326 	// Read each information line (one line per build results)
    327 	Object[] scenarios = this.results.getChildren(null);
    328 	ConfigResultsElement[] configs = new ConfigResultsElement[scenarios.length];
    329 	int row = 0;
    330 	while (lines.hasNext()) {
    331 		List line = (List) lines.next();
    332 		int size = line.size();
    333 
    334 		// The first column is the build name
    335 		String buildName = (String) line.get(0);
    336 		String milestoneName = Util.getMilestoneName(buildName);
    337 		TableItem item = null;
    338 
    339 		// Set item if the line is not filtered
    340 		Font italic;
    341 		if (milestoneName != null) {
    342 			item = new TableItem (this.table, SWT.NONE);
    343 			item.setText(milestoneName + " - " + buildName);
    344 			item.setFont(0, this.boldFont);
    345 			if (!onlyMilestones) item.setBackground(this.blueref);
    346 			italic = this.boldItalicFont;
    347 		} else {
    348 			if ((onlyMilestones && Util.getNextMilestone(buildName) != null) ||
    349 				(skipNightlyBuilds && buildName.charAt(0) == 'N')) {
    350 				// skip line
    351 				continue;
    352 			}
    353 			item = new TableItem (this.table, SWT.NONE);
    354 			item.setText(0, buildName);
    355 			italic = this.italicFont;
    356 		}
    357 
    358 		// Read each column value
    359 		String baselineName = null;
    360 		for (int col=1; col<size; col++) {
    361 
    362 			// Reset tooltip info
    363 			String toolTipText = null;
    364 			String toolTipMessage = null;
    365 			int toolTipStyle = SWT.BALLOON;
    366 
    367 			// Otherwise get values for a scenario
    368 			Font italic2 = italic;
    369 			ScenarioResultsElement scenarioResultsElement = (ScenarioResultsElement) scenarios[col-1];
    370 			if (milestoneName != null || (!fingerprints && scenarioResultsElement.hasSummary())) {
    371 				italic2 = this.boldItalicFont;
    372 				item.setFont(col, this.boldFont);
    373 			}
    374 			// Otherwise get values for a scenario
    375 			double[] values = (double[]) line.get(col);
    376 			if (values == AbstractResults.NO_BUILD_RESULTS) {
    377 				item.setText(col, "Missing");
    378 				item.setForeground(col, GRAY);
    379 				item.setFont(col, italic2);
    380 			} else if (values == AbstractResults.INVALID_RESULTS) {
    381 				item.setText(col, "Invalid");
    382 				item.setForeground(col, RED);
    383 				item.setFont(col, italic2);
    384 			} else {
    385 				// Get values array
    386 				double buildValue = values[AbstractResults.BUILD_VALUE_INDEX];
    387 				double baselineValue = values[AbstractResults.BASELINE_VALUE_INDEX];
    388 				double delta = values[AbstractResults.DELTA_VALUE_INDEX];
    389 				double error = values[AbstractResults.DELTA_ERROR_INDEX];
    390 
    391 				// Set text with delta value
    392 				item.setText(col, Util.PERCENTAGE_FORMAT.format(delta));
    393 
    394 				// Compute the tooltip to display on the cell
    395 				if (error > 0.03) {
    396 					// error is over the 3% threshold
    397 					item.setImage(col, ResultsElement.WARN_IMAGE);
    398 					item.setForeground(col, this.darkyellow);
    399 					toolTipText = "May be not reliable";
    400 					toolTipMessage = "The error on this result is "+Util.PERCENTAGE_FORMAT.format(error)+", hence it may be not reliable";
    401 					toolTipStyle |= SWT.ICON_WARNING;
    402 				}
    403 				if (delta < -0.1) {
    404 					// delta < -10%: failure shown by an red-cross icon + text in red
    405 					item.setImage(col, ResultsElement.ERROR_IMAGE);
    406 					item.setForeground(col, RED);
    407 				} else if (delta < -0.05) {
    408 					// negative delta over 5% shown in red
    409 					item.setForeground(col, RED);
    410 				} else if (delta < 0) {
    411 					// negative delta shown in magenta
    412 					item.setForeground(col, MAGENTA);
    413 				} else if (delta > 0.2) {
    414 					// positive delta over 20% shown in green
    415 					item.setForeground(col, DARK_GREEN);
    416 				} else if (delta > 0.1) {
    417 					// positive delta between 10% and 20% shown in blue
    418 					item.setForeground(col, BLUE);
    419 				}
    420 
    421 				// Moderate the status if the build value or the difference is small
    422 				if (delta < 0) {
    423 					double diff = Math.abs(baselineValue - buildValue);
    424 					if (buildValue < 100 || diff < 100) {
    425 						if (toolTipText == null) {
    426 							toolTipText = "";
    427 						} else {
    428 							toolTipText += ", ";
    429 						}
    430 						toolTipText += "Small value";
    431 						if (toolTipMessage == null) {
    432 							toolTipMessage = "";
    433 						} else {
    434 							toolTipMessage += ".\n";
    435 						}
    436 						if (buildValue < 100) {
    437 							toolTipMessage += "This test has a small value ("+buildValue+"ms)";
    438 						} else {
    439 							toolTipMessage += "This test variation has a small value ("+diff+"ms)";
    440 						}
    441 						toolTipMessage +=  ", hence it may not be necessary to spend time on fixing this possible regression";
    442 						// set the text in italic
    443 						item.setFont(col, italic2);
    444 						toolTipStyle |= SWT.ICON_INFORMATION;
    445 					}
    446 				}
    447 
    448 				// Add information in tooltip when history shows big variation
    449 				ConfigResultsElement configResultsElement = configs[col-1];
    450 				if (configResultsElement == null) {
    451 					configResultsElement = (ConfigResultsElement) scenarioResultsElement.getResultsElement(this.configName);
    452 					configs[col-1] = configResultsElement;
    453 				}
    454 				double deviation = configResultsElement == null ? 0 : configResultsElement.getStatistics()[3];
    455 				if (deviation > 0.2) {
    456 					// deviation is over 20% over the entire history
    457 					if (toolTipText == null) {
    458 						toolTipText = "";
    459 					} else {
    460 						toolTipText += ", ";
    461 					}
    462 					toolTipText += "History shows erratic values";
    463 					if (toolTipMessage == null) {
    464 						toolTipMessage = "";
    465 					} else {
    466 						toolTipMessage += ".\n";
    467 					}
    468 					toolTipMessage += "The results history shows that the variation of its delta is over 20% ("+Util.PERCENTAGE_FORMAT.format(deviation)+"), hence its result is surely not reliable";
    469 					// set the text in italic
    470 					item.setFont(col, italic2);
    471 					toolTipStyle |= SWT.ICON_INFORMATION;
    472 				} else if (deviation > 0.1) { // moderate the status when the test
    473 					// deviation is between 10% and 20% over the entire history
    474 					if (toolTipText == null) {
    475 						toolTipText = "";
    476 					} else {
    477 						toolTipText += ", ";
    478 					}
    479 					toolTipText += "History shows unstable values";
    480 					if (toolTipMessage == null) {
    481 						toolTipMessage = "";
    482 					} else {
    483 						toolTipMessage += ".\n";
    484 					}
    485 					toolTipMessage += "The results history shows that the variation of its delta is between 10% and 20% ("+Util.PERCENTAGE_FORMAT.format(deviation)+"), hence its result may not be really reliable";
    486 					// set the text in italic
    487 					item.setFont(col, italic2);
    488 					if (toolTipStyle == SWT.BALLOON && delta >= -0.1) {
    489 						toolTipStyle |= SWT.ICON_INFORMATION;
    490 					} else {
    491 						// reduce icon severity from error to warning
    492 						toolTipStyle |= SWT.ICON_WARNING;
    493 					}
    494 				}
    495 			}
    496 
    497 			// Set tooltip
    498 			if (toolTipText != null) {
    499 				createToolTip(toolTipText, toolTipMessage, toolTipStyle, new Point(col, row));
    500 			}
    501 
    502 			// Baseline name
    503 			ConfigResultsElement configResultsElement = (ConfigResultsElement) scenarioResultsElement.getResultsElement(this.configName);
    504 			if (configResultsElement != null) {
    505 				String configBaselineName = configResultsElement.getBaselineBuildName(buildName);
    506 				if (baselineName == null) {
    507 					baselineName = configBaselineName;
    508 				} else if (baselineName.indexOf(configBaselineName) < 0) {
    509 					baselineName += ", " +configBaselineName;
    510 				}
    511 			}
    512 		}
    513 
    514 		// Set the tooltip over the build name
    515 		if (baselineName != null) {
    516 			createToolTip(buildName, "Baseline: "+baselineName, SWT.BALLOON | SWT.ICON_INFORMATION, new Point(0, row));
    517 		}
    518 
    519 		// Increment row counter
    520 		row++;
    521 	}
    522 	this.rowsCount = row;
    523 }
    524 
    525 /*
    526  * Get the columns name.
    527  */
    528 private String[] getLayoutDataFieldNames(boolean fingerprints) {
    529 	if (this.results == null) {
    530 		return new String[0];
    531 	}
    532 	List labels = this.results.getScenariosLabels(fingerprints);
    533 	labels.add(0, "Build");
    534 	String[] names = new String[labels.size()];
    535 	labels.toArray(names);
    536 	return names;
    537 }
    538 
    539 /*
    540  * The tab text is the full machine name.
    541  */
    542 public String getTabText() {
    543 	return this.configBox;
    544 }
    545 
    546 /*
    547  * Init the SWT resources
    548  */
    549 private void initResources() {
    550 	// Fonts
    551 	String fontDataName = this.gc.getFont().getFontData()[0].toString();
    552 	FontData fdItalic = new FontData(fontDataName);
    553 	fdItalic.setStyle(SWT.ITALIC);
    554 	this.italicFont = new Font(this.display, fdItalic);
    555 	FontData fdBold = new FontData(fontDataName);
    556 	fdBold.setStyle(SWT.BOLD);
    557 	this.boldFont = new Font(this.display, fdBold);
    558 	FontData fdBoldItalic = new FontData(fontDataName);
    559 	fdBoldItalic.setStyle(SWT.BOLD | SWT.ITALIC);
    560 	this.boldItalicFont = new Font(this.display, fdBoldItalic);
    561 
    562 	// Colors
    563 	this.lightred = new Color(DEFAULT_DISPLAY, 220, 50, 50);
    564 	this.lightyellow = new Color(DEFAULT_DISPLAY, 255, 255, 160);
    565 	this.darkyellow = new Color(DEFAULT_DISPLAY, 160, 160, 0);
    566 	this.blueref = new Color(DEFAULT_DISPLAY, 200, 200, 255);
    567 }
    568 
    569 /*
    570  * Select the line corresponding to the given build.
    571  */
    572 void select(BuildResultsElement buildResultsElement) {
    573 	int count = this.table.getItemCount();
    574 	String buildName = buildResultsElement.getName();
    575 	TableItem[] items = this.table.getItems();
    576 	for (int i=0; i<count; i++) {
    577 		if (items[i].getText().endsWith(buildName)) {
    578 			this.table.deselect(this.table.getSelectionIndex());
    579 			this.table.select(i);
    580 			return;
    581 		}
    582 	}
    583 }
    584 }
    585