blob: f3ed4346d9c2743b682a0327e22d3cdabd0a212f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.test.internal.performance.results.ui;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.ToolTip;
import org.eclipse.test.internal.performance.results.db.AbstractResults;
import org.eclipse.test.internal.performance.results.db.BuildResults;
import org.eclipse.test.internal.performance.results.db.ConfigResults;
import org.eclipse.test.internal.performance.results.db.PerformanceResults;
import org.eclipse.test.internal.performance.results.db.ScenarioResults;
import org.eclipse.test.internal.performance.results.model.BuildResultsElement;
import org.eclipse.test.internal.performance.results.model.PerformanceResultsElement;
import org.eclipse.test.internal.performance.results.model.ResultsElement;
import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
import org.eclipse.test.internal.performance.results.utils.Util;
/**
* Tab to display all performances results numbers for a configuration (i.e a performance machine).
*/
public class BuildsComparisonTab {
// Colors
static final Display DEFAULT_DISPLAY = Display.getDefault();
static final Color BLUE= DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_BLUE);
static final Color DARK_GREEN= DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_DARK_GREEN);
static final Color GRAY = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_GRAY);
static final Color MAGENTA = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_MAGENTA);
static final Color RED = DEFAULT_DISPLAY.getSystemColor(SWT.COLOR_RED);
// SWT resources
Shell shell;
Display display;
Table table;
private GC gc;
private Color lightred;
private Color lightyellow;
private Color darkyellow;
private Color blueref;
private Font boldFont;
private Font italicFont;
private Font boldItalicFont;
Map<Point, ToolTip> toolTips;
// Information
String componentName;
double[][] allValues;
double[][] allErrors;
// Cells management
Point tableOrigin, tableSize;
int columnsCount, rowsCount;
List<List<Object>> firstLine;
// Eclipse preferences
private IEclipsePreferences preferences;
/*
* Default constructor.
*/
public BuildsComparisonTab(String name) {
this.componentName = name;
this.preferences = InstanceScope.INSTANCE.getNode(IPerformancesConstants.PLUGIN_ID);
}
/**
* Creates the tab folder page.
*
* @param view The view on which to create the tab folder page
* @return the new page for the tab folder
*/
Composite createTabFolderPage (BuildsComparisonView view) {
// Cache the shell and display.
this.shell = view.tabFolder.getShell();
this.display = this.shell.getDisplay();
// Remove old table if present
boolean initResources = this.table == null;
if (this.table != null) {
disposeTable();
}
// Create the "children" table
int style = SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION;
this.table = new Table(view.tabFolder, style);
this.table.setLinesVisible (true);
this.table.setHeaderVisible (true);
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1);
gridData.heightHint = 150;
this.table.setLayoutData (gridData);
this.gc = new GC(this.table);
// Init resources
if (initResources) initResources();
// Add columns to the table
TableColumn firstColumn = new TableColumn(this.table, SWT.CENTER);
firstColumn.setText("");
PerformanceResultsElement results = view.getResults();
String [] configDescriptions = results.getConfigDescriptions();
int length = configDescriptions.length;
for (int i = 0; i < length; i++) {
TableColumn column = new TableColumn(this.table, SWT.CENTER);
column.setText(configDescriptions[i]);
}
// Add lines to the table
this.toolTips = new HashMap<>();
boolean fingerprints = this.preferences.getBoolean(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS, IPerformancesConstants.DEFAULT_FILTER_ADVANCED_SCENARIOS);
fillTable(results, view.currentBuild, view.referenceBuild, fingerprints);
// Updated columns
for (int i=0; i<=length; i++) {
TableColumn column = this.table.getColumn(i);
column.setWidth(i==0?120:100);
}
// Store table info
this.columnsCount = length;
// Listen to mouse track events to display the table cell corresponding tooltip.
MouseTrackListener mouseTrackListener = new MouseTrackListener() {
ToolTip currentTooltip;
@Override
public void mouseHover(MouseEvent e) {
if (this.currentTooltip != null) {
this.currentTooltip.setVisible(false);
this.currentTooltip = null;
}
Point cellPosition = currentCellPosition(e.x, e.y);
if (cellPosition != null) {
ToolTip tooltip = BuildsComparisonTab.this.toolTips.get(cellPosition);
if (tooltip != null) {
Point location = BuildsComparisonTab.this.table.toDisplay(new Point(e.x, e.y));
tooltip.setLocation(location);
tooltip.setVisible(true);
this.currentTooltip = tooltip;
}
}
}
@Override
public void mouseEnter(MouseEvent e) {
}
@Override
public void mouseExit(MouseEvent e) {
}
};
this.table.addMouseTrackListener(mouseTrackListener);
// Select the first line by default (as this is the current build)
this.table.select(0);
// Return the built composite
return this.table;
}
/*
* Create and store a tooltip with the given information and at the given position.
*/
void createToolTip(String toolTipText, String toolTipMessage, int toolTipStyle, Point position) {
ToolTip toolTip = new ToolTip(this.table.getShell(), toolTipStyle);
toolTip.setAutoHide(true);
toolTip.setText(toolTipText);
toolTip.setMessage(/*"("+col+","+row+") "+*/toolTipMessage);
this.toolTips.put(position, toolTip);
}
/*
* Get the current cell position (column, row) from a point position.
*/
Point currentCellPosition(int x, int y) {
// Compute the origin of the visible area
if (this.tableOrigin == null) {
this.tableOrigin = new Point(0, this.table.getHeaderHeight());
}
// Increment width until over current y position
int height= this.tableOrigin.y;
int row = this.table.getTopIndex();
while (row<this.rowsCount && height<y) {
height += this.table.getItemHeight();
row++;
}
if (height < y) {
// return when position is over the last line
return null;
}
row--;
// Increment width until being over current x position
int col = 0;
TableItem tableItem = this.table.getItem(row);
Rectangle bounds = tableItem.getBounds(col);
while (col<this.columnsCount) {
int max = bounds.x + bounds.width + this.table.getGridLineWidth();
if (x <= max) break;
if (col == this.columnsCount) {
// return when position is over the last column
return null;
}
col++;
bounds = tableItem.getBounds(col);
}
// Return the found table cell position
return new Point(col, row);
}
/*
* Dispose all SWT resources.
*/
public void dispose() {
if (this.boldFont != null) {
this.boldFont.dispose();
}
if (this.italicFont != null) {
this.italicFont.dispose();
}
if (this.boldItalicFont != null) {
this.boldItalicFont.dispose();
}
if (this.darkyellow != null) {
this.darkyellow.dispose();
}
if (this.lightyellow != null) {
this.lightyellow.dispose();
}
if (this.lightred != null) {
this.lightred.dispose();
}
if (this.blueref != null) {
this.blueref.dispose();
}
disposeTable();
}
/*
* Dispose all SWT controls associated with the table.
*/
private void disposeTable() {
if (this.toolTips != null) {
Iterator<Point> cells = this.toolTips.keySet().iterator();
while (cells.hasNext()) {
ToolTip toolTip = this.toolTips.get(cells.next());
toolTip.dispose();
}
}
this.table.dispose();
this.tableOrigin = null;
this.firstLine = null;
}
/*
* Fill the lines of the tables.
* Get all the information from the model which are returned in a list (lines) of lists (columns).
*/
private void fillTable(PerformanceResultsElement results, String currentBuild, String referenceBuild, boolean fingerprints) {
// Get all the scenario for the component
final PerformanceResults performanceResults = results.getPerformanceResults();
List<AbstractResults> scenarios = performanceResults.getComponentScenarios(this.componentName);
int size = scenarios.size();
// Get thresholds
int failurePref = this.preferences.getInt(IPerformancesConstants.PRE_COMPARISON_THRESHOLD_FAILURE, IPerformancesConstants.DEFAULT_COMPARISON_THRESHOLD_FAILURE);
int errorPref = this.preferences.getInt(IPerformancesConstants.PRE_COMPARISON_THRESHOLD_ERROR, IPerformancesConstants.DEFAULT_COMPARISON_THRESHOLD_ERROR);
int improvementPref = this.preferences.getInt(IPerformancesConstants.PRE_COMPARISON_THRESHOLD_IMPROVEMENT, IPerformancesConstants.DEFAULT_COMPARISON_THRESHOLD_IMPROVEMENT);
final double failureThreshold = -failurePref / 100.0;
final double errorThreshold = errorPref / 100.0;
final double improvementThreshold = improvementPref / 100.0;
// Build the map made of one line per scenario results
final String[] configs = performanceResults.getConfigNames(true/*sort*/);
for (int i=0; i<size; i++) {
ScenarioResults scenarioResults = (ScenarioResults) scenarios.get(i);
if (!scenarioResults.isValid()) continue;
final boolean fingerprint = scenarioResults.hasSummary();
if (!fingerprints || fingerprint) {
// The first column is the scenario name
String scenarioName = scenarioResults.getShortName();
TableItem item = new TableItem (this.table, SWT.NONE);
item.setText(scenarioName);
// Bold font and blue ref background color if this is a fingerprint test
Font italic;
if (fingerprint) {
item.setFont(0, this.boldFont);
item.setBackground(this.blueref);
italic = this.boldItalicFont;
} else {
italic = this.italicFont;
}
// Fill config columns
int length = configs.length;
for (int j=0; j<length; j++) {
// See whether there's available numbers
int col = j+1;
final ConfigResults configResults = scenarioResults.getConfigResults(configs[j]);
if (configResults == null || !configResults.isValid()) {
item.setText(col, "Invalid");
item.setForeground(col, GRAY);
item.setFont(col, italic);
continue;
}
final BuildResults buildResults = configResults.getBuildResults(currentBuild);
if (buildResults == null) {
item.setText(col, "Missing results");
item.setForeground(col, GRAY);
item.setFont(col, italic);
continue;
}
final BuildResults referenceResults = configResults.getBuildResults(referenceBuild);
if (referenceResults == null) {
item.setText(col, "Missing ref");
item.setForeground(col, GRAY);
item.setFont(col, italic);
continue;
}
// Get numbers and infos
double[] values = configResults.getNumbers(buildResults, referenceResults);
// Reset tooltip info
String toolTipText = null;
String toolTipMessage = null;
int toolTipStyle = SWT.BALLOON;
// Get values array
final double buildValue = values[AbstractResults.BUILD_VALUE_INDEX];
final double referenceValue = values[AbstractResults.BASELINE_VALUE_INDEX];
final double delta = values[AbstractResults.DELTA_VALUE_INDEX];
final double error = values[AbstractResults.DELTA_ERROR_INDEX];
// Set text with delta value
final StringBuilder address = new StringBuilder("http://fullmoon.ottawa.ibm.com/downloads/drops/");
address.append(currentBuild);
address.append("/performance/");
address.append(configResults.getName());
address.append('/');
address.append(scenarioResults.getFileName());
address.append(".html");
StringBuilder buffer = new StringBuilder("<a href=\"");
buffer.append(address);
buffer.append("\">");
final String itemText = Util.PERCENTAGE_FORMAT.format(delta);
buffer.append(itemText);
buffer.append("</a>");
// Simple text
item.setText(col, itemText);
/* Link + Editor
Link link = new Link(this.table, SWT.CENTER);
link.setText(buffer.toString());
link.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
Program.launch(address.toString());
}
});
final TableEditor editor = new TableEditor(this.table);
editor.grabHorizontal = editor.grabVertical = true;
editor.verticalAlignment = SWT.CENTER;
editor.horizontalAlignment = SWT.CENTER;
editor.setEditor(link, item, col);
*/
// Compute the tooltip to display on the cell
if (error > errorThreshold) {
// error is over the threshold
item.setForeground(col, this.darkyellow);
toolTipText = "May be not reliable";
toolTipMessage = "The error on this result is "+Util.PERCENTAGE_FORMAT.format(error)+", hence it may be not reliable";
toolTipStyle |= SWT.ICON_WARNING;
}
if (delta < -0.1) {
// delta < -10%: failure shown by an red-cross icon + text in red
item.setImage(col, ResultsElement.ERROR_IMAGE);
}
if (delta < failureThreshold) {
// negative delta over the threshold shown in red
item.setForeground(col, RED);
} else if (delta > improvementThreshold) {
// positive delta over the threshold are shown in green
item.setForeground(col, DARK_GREEN);
}
// Moderate the status if the build value or the difference is small
if (buildValue < 100 || referenceValue < 100) {
if (toolTipText == null) {
toolTipText = "";
} else {
toolTipText += ", ";
}
toolTipText += "Small value";
if (toolTipMessage == null) {
toolTipMessage = "";
} else {
toolTipMessage += ".\n";
}
toolTipMessage += "This test has a small value ("+buildValue+"ms)";
toolTipStyle |= SWT.ICON_WARNING;
item.setImage(col, ResultsElement.WARN_IMAGE);
}
// Add information in tooltip when history shows big variation
double deviation = configResults.getStatistics(Util.BASELINE_BUILD_PREFIXES)[3];
if (deviation > 0.2) {
// deviation is over 20% over the entire history
if (toolTipText == null) {
toolTipText = "";
} else {
toolTipText += ", ";
}
toolTipText += "History shows erratic values";
if (toolTipMessage == null) {
toolTipMessage = "";
} else {
toolTipMessage += ".\n";
}
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";
// set the text in italic
item.setFont(col, italic);
toolTipStyle |= SWT.ICON_INFORMATION;
} else if (deviation > 0.1) { // moderate the status when the test
// deviation is between 10% and 20% over the entire history
if (toolTipText == null) {
toolTipText = "";
} else {
toolTipText += ", ";
}
toolTipText += "History shows unstable values";
if (toolTipMessage == null) {
toolTipMessage = "";
} else {
toolTipMessage += ".\n";
}
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";
// set the text in italic
item.setFont(col, italic);
if (toolTipStyle == SWT.BALLOON && delta >= -0.1) {
toolTipStyle |= SWT.ICON_INFORMATION;
} else {
// reduce icon severity from error to warning
toolTipStyle |= SWT.ICON_WARNING;
}
}
// Set tooltip
if (toolTipText != null) {
createToolTip(toolTipText, toolTipMessage, toolTipStyle, new Point(col, i));
}
}
}
}
this.rowsCount = size;
}
protected Shell getShell() {
return this.shell;
}
/*
* The tab text is the full machine name.
*/
public String getTabText() {
return Util.componentDisplayName(this.componentName);
}
/*
* Init the SWT resources
*/
private void initResources() {
// Fonts
String fontDataName = this.gc.getFont().getFontData()[0].toString();
FontData fdItalic = new FontData(fontDataName);
fdItalic.setStyle(SWT.ITALIC);
this.italicFont = new Font(this.display, fdItalic);
FontData fdBold = new FontData(fontDataName);
fdBold.setStyle(SWT.BOLD);
this.boldFont = new Font(this.display, fdBold);
FontData fdBoldItalic = new FontData(fontDataName);
fdBoldItalic.setStyle(SWT.BOLD | SWT.ITALIC);
this.boldItalicFont = new Font(this.display, fdBoldItalic);
// Colors
this.lightred = new Color(DEFAULT_DISPLAY, 220, 50, 50);
this.lightyellow = new Color(DEFAULT_DISPLAY, 255, 255, 160);
this.darkyellow = new Color(DEFAULT_DISPLAY, 160, 160, 0);
this.blueref = new Color(DEFAULT_DISPLAY, 200, 200, 255);
}
/*
* Select the line corresponding to the given build.
*/
void select(BuildResultsElement buildResultsElement) {
int count = this.table.getItemCount();
String buildName = buildResultsElement.getName();
TableItem[] items = this.table.getItems();
for (int i=0; i<count; i++) {
if (items[i].getText().endsWith(buildName)) {
this.table.deselect(this.table.getSelectionIndex());
this.table.select(i);
return;
}
}
}
}