HEAD - Add Builds Comparison View
diff --git a/bundles/org.eclipse.test.performance.ui/icons/compare_view.gif b/bundles/org.eclipse.test.performance.ui/icons/compare_view.gif
new file mode 100644
index 0000000..dc549bc
--- /dev/null
+++ b/bundles/org.eclipse.test.performance.ui/icons/compare_view.gif
Binary files differ
diff --git a/bundles/org.eclipse.test.performance.ui/performanceui.jar b/bundles/org.eclipse.test.performance.ui/performanceui.jar
index 82efe4f..bb3394a 100644
--- a/bundles/org.eclipse.test.performance.ui/performanceui.jar
+++ b/bundles/org.eclipse.test.performance.ui/performanceui.jar
Binary files differ
diff --git a/bundles/org.eclipse.test.performance.ui/plugin.xml b/bundles/org.eclipse.test.performance.ui/plugin.xml
index 491cb79..04d80d3 100644
--- a/bundles/org.eclipse.test.performance.ui/plugin.xml
+++ b/bundles/org.eclipse.test.performance.ui/plugin.xml
@@ -49,6 +49,14 @@
             name="Component Results"
             restorable="true">
       </view>
+      <view
+            category="Performances"
+            class="org.eclipse.test.internal.performance.results.ui.BuildsComparisonView"
+            icon="icons/compare_view.gif"
+            id="org.eclipse.test.internal.performance.results.ui.BuildsComparisonView"
+            name="Builds comparison"
+            restorable="true">
+      </view>
    </extension>
    <extension
          point="org.eclipse.core.runtime.preferences">
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/model/PerformanceResultsElement.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/model/PerformanceResultsElement.java
index e8bb640..d4c108c 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/model/PerformanceResultsElement.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/model/PerformanceResultsElement.java
@@ -20,7 +20,7 @@
 import java.util.Arrays;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.test.internal.performance.results.db.*;
-import org.eclipse.test.internal.performance.results.ui.ScenariosComparisonTable;
+import org.eclipse.test.internal.performance.results.ui.BuildsComparisonTable;
 import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
 import org.eclipse.test.internal.performance.results.utils.Util;
 import org.eclipse.test.performance.ui.Utils;
@@ -363,7 +363,7 @@
 		try {
 			int length = this.children.length;
 			for (int i=0; i<length; i++) {
-				ScenariosComparisonTable table = new ScenariosComparisonTable(this.children[i].getName(), stream, build, reference);
+				BuildsComparisonTable table = new BuildsComparisonTable(this.children[i].getName(), stream, build, reference);
 				table.print(getPerformanceResults());
 			}
 		}
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonTab.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonTab.java
new file mode 100644
index 0000000..abcbcb1
--- /dev/null
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonTab.java
@@ -0,0 +1,506 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2009 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 toolTips;
+
+	// Information
+	String componentName;
+	double[][] allValues;
+	double[][] allErrors;
+
+	// Cells management
+	Point tableOrigin, tableSize;
+	int columnsCount, rowsCount;
+	List firstLine;
+
+	// Eclipse preferences
+	private IEclipsePreferences preferences;
+
+/*
+ * Default constructor.
+ */
+public BuildsComparisonTab(String name) {
+    this.componentName = name;
+	this.preferences = new InstanceScope().getNode(IPerformancesConstants.PLUGIN_ID);
+}
+
+/**
+ * Creates the tab folder page.
+ *
+ * @param tabFolder org.eclipse.swt.widgets.TabFolder
+ * @param fullSelection Tells whether the table should have a full line selection or not
+ * @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;
+		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 = (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;
+				}
+			}
+		}
+		public void mouseEnter(MouseEvent e) {
+		}
+		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 cells = this.toolTips.keySet().iterator();
+		while (cells.hasNext()) {
+			ToolTip 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 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
+				item.setText(col, Util.PERCENTAGE_FORMAT.format(delta));
+
+				// 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;
+}
+
+/*
+ * 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;
+		}
+	}
+}
+}
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/ScenariosComparisonTable.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonTable.java
similarity index 97%
rename from bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/ScenariosComparisonTable.java
rename to bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonTable.java
index fcfb69c..440c052 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/ScenariosComparisonTable.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonTable.java
@@ -25,14 +25,14 @@
  * This class is responsible to print html table with the difference between two
  * builds for all the scenarios and the configuration of a specific component.
  */
-public class ScenariosComparisonTable {
+public class BuildsComparisonTable {
 
 	private static final double DEFAULT_FAILURE_THRESHOLD = PerformanceResults.DEFAULT_FAILURE_THRESHOLD / 100.0 / 2;
 	private String component;
 	private PrintStream stream;
 	String buildName, referenceName;
 
-public ScenariosComparisonTable(String name, PrintStream stream, String build, String reference) {
+public BuildsComparisonTable(String name, PrintStream stream, String build, String reference) {
     this.component = name;
     this.stream = stream;
     this.buildName = build;
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonView.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonView.java
new file mode 100644
index 0000000..d27eaca
--- /dev/null
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsComparisonView.java
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2009 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 org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TreeSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Table;
+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.utils.IPerformancesConstants;
+import org.eclipse.test.internal.performance.results.utils.Util;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IMemento;
+import org.eclipse.ui.IViewSite;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.ViewPart;
+
+
+/**
+ * This view shows the difference between two selected builds.
+ * <p>
+ * Each component are shown in a separate tab.
+ * </p><p>
+ * The displayed results can be written in a file, either all component or only
+ * the current one.
+ * </p><p>
+ * It's also possible to filter the scenario to display only the fingerprint ones.
+ * <br>
+ * Note that this filter is synchronized with the one applied in the
+ * {@link ComponentsView Components view}.
+ * </p>
+ *
+ * @see ConfigTab Folder tab containing all the results for a configuration.
+ */
+public class BuildsComparisonView extends ViewPart implements ISelectionChangedListener, IPreferenceChangeListener {
+
+	// SWT resources
+	CTabFolder tabFolder;
+
+	// Model information
+	BuildsComparisonTab[] tabs;
+	PerformanceResultsElement results;
+
+	// Action
+	Action filterAdvancedScenarios;
+
+	// Views
+	BuildsView buildsView;
+	IMemento viewState;
+
+	// Eclipse preferences
+	IEclipsePreferences preferences;
+
+	// Comparison
+	String currentBuild;
+	String referenceBuild;
+
+/*
+ * Default constructor:
+ * 	- create the image descriptor
+ * 	- register the view as a properties listener
+ */
+public BuildsComparisonView() {
+	this.preferences = new InstanceScope().getNode(IPerformancesConstants.PLUGIN_ID);
+	this.preferences.addPreferenceChangeListener(this);
+	// TODO should be done only once!
+	Util.initMilestones(this.preferences);
+}
+
+/*
+ * Contribute the local tools bar and the pull-down menu to the action bars.
+ */
+void contributeToActionBars() {
+	IActionBars bars = getViewSite().getActionBars();
+	fillLocalPullDown(bars.getMenuManager());
+	fillLocalToolBar(bars.getToolBarManager());
+}
+
+/*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
+ */
+public void createPartControl(Composite parent) {
+
+	// Create the tab folder
+	this.tabFolder = new CTabFolder(parent, SWT.BORDER);
+
+	// Add results view as listener to viewer selection changes
+	Display.getDefault().asyncExec(new Runnable() {
+		public void run() {
+			PerformancesView performancesView = (PerformancesView) PerformancesView.getWorkbenchView("org.eclipse.test.internal.performance.results.ui.ComponentsView");
+			if (performancesView != null) {
+				performancesView.viewer.addSelectionChangedListener(BuildsComparisonView.this);
+			}
+		}
+	});
+
+	// Set actions
+	PlatformUI.getWorkbench().getHelpSystem().setHelp(this.tabFolder, "org.eclipse.test.performance.ui.results");
+	makeActions();
+	contributeToActionBars();
+
+	// Restore state
+	restoreState();
+
+	// Create tabs
+	createTabs();
+
+	// Set selections (tab and line)
+	this.tabFolder.setSimple(false);
+}
+
+/*
+ * Create the tab folder pages. There's one tab per performance machine.
+ * The list of these machines is got from the DB_Results contants.
+ */
+void createTabs() {
+	if (this.currentBuild == null || this.referenceBuild == null) return;
+	PerformanceResultsElement performanceResultsElement = getBuildsView().results;
+	String[] components = performanceResultsElement.getComponents();
+	int length = components.length;
+	this.tabs = new BuildsComparisonTab[length];
+	for (int i=0; i<length; i++) {
+		this.tabs[i] = new BuildsComparisonTab(components[i]);
+	}
+	for (int i=0; i<this.tabs.length; i++) {
+		CTabItem item = new CTabItem (this.tabFolder, SWT.NONE);
+		item.setText (this.tabs[i].getTabText ());
+		item.setControl (this.tabs[i].createTabFolderPage(this));
+		item.setData (this.tabs[i]);
+	}
+	this.tabFolder.setSelection(0);
+}
+
+/*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.part.WorkbenchPart#dispose()
+ */
+public void dispose() {
+	this.tabFolder.dispose();
+	int length = this.tabs==null ? 0 : this.tabs.length;
+	for (int i=0; i<length; i++) {
+		this.tabs[i].dispose();
+	}
+	super.dispose();
+}
+
+/*
+ * Fill the filters drop-down menu with:
+ * 	- filter nightly builds
+ * 	- filter non-milestone builds
+ *	- filter non-fingerprint scenarios
+ */
+void fillFiltersDropDown(IMenuManager manager) {
+	manager.add(this.filterAdvancedScenarios);
+}
+
+/*
+ * Fill the local pull down menu.
+ */
+void fillLocalPullDown(IMenuManager manager) {
+	MenuManager filtersManager= new MenuManager("Filters");
+	fillFiltersDropDown(filtersManager);
+	manager.add(filtersManager);
+}
+
+/*
+ * Fill the local tool bar with:
+ * 	- change line selection display
+ */
+void fillLocalToolBar(IToolBarManager manager) {
+	//manager.add(this.fullLineSelection);
+}
+
+/*
+ * Return the components results view.
+ */
+PerformanceResultsElement getResults() {
+	if (this.results == null) {
+		this.results = getBuildsView().results;
+	}
+	return this.results;
+}
+
+/*
+ * Return the components results view.
+ */
+BuildsView getBuildsView() {
+	if (this.buildsView == null) {
+		this.buildsView = (BuildsView) PerformancesView.getWorkbenchView("org.eclipse.test.internal.performance.results.ui.BuildsView");
+	}
+	return this.buildsView;
+}
+
+/*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)
+ */
+public void init(IViewSite site, IMemento memento) throws PartInitException {
+	super.init(site, memento);
+	this.viewState = memento;
+}
+
+/*
+ * Make the actions of the view:
+ * 	- change table line selection display
+ * 	- filter nightly builds
+ * 	- filter non-milestone builds
+ *	- filter non-fingerprint scenarios
+ */
+void makeActions() {
+
+	// Filter non-fingerprints action
+	this.filterAdvancedScenarios = new Action("Advanced &Scenarios", IAction.AS_CHECK_BOX) {
+		public void run() {
+			BuildsComparisonView.this.preferences.putBoolean(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS, isChecked());
+			resetTabFolders(false/*refresh*/);
+        }
+	};
+	this.filterAdvancedScenarios.setChecked(true);
+	this.filterAdvancedScenarios.setToolTipText("Filter advanced scenarios (i.e. not fingerprint ones)");
+}
+
+/* (non-Javadoc)
+ * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener#preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent)
+ */
+public void preferenceChange(PreferenceChangeEvent event) {
+	String propertyName = event.getKey();
+	Object newValue = event.getNewValue();
+
+	// Filter non-fingerprints change
+	if (propertyName.equals(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS)) {
+		boolean checked = newValue == null ? IPerformancesConstants.DEFAULT_FILTER_ADVANCED_SCENARIOS : "true".equals(newValue);
+		this.filterAdvancedScenarios.setChecked(checked);
+		resetTabFolders(false/*refresh*/);
+	}
+}
+
+/*
+ * Reset the table tab folders by re-create all the pages.
+ * Selections are set onto the first found error if this is the first tab creation (typically on a component change event from the ComponentsView)
+ * or to the previous one if this is just a refresh.
+ */
+void resetTabFolders(boolean init) {
+
+	// Store current indexes
+	int tabIndex = this.tabFolder.getSelectionIndex();
+	int lineIndex = tabIndex<0 ? -1 : this.tabs[tabIndex].table.getSelectionIndex();
+
+	// Create tab folders
+	CTabItem[] tabItems = this.tabFolder.getItems();
+	int length = tabItems.length;
+	if (length == 0) {
+		createTabs();
+	} else {
+		for (int i=0; i<length; i++) {
+			tabItems[i].setControl(this.tabs [i].createTabFolderPage(this));
+		}
+	}
+
+	// Set part name
+	setPartName(this.currentBuild+" vs. "+this.referenceBuild);
+
+	// Set the selection
+	if (tabIndex >= 0 && lineIndex >= 0) {
+		this.tabFolder.setSelection(tabIndex);
+		Table table = this.tabs[tabIndex].table;
+		table.setSelection(lineIndex);
+	}
+}
+
+/*
+ * Restore the view state from the memento information.
+ */
+void restoreState() {
+
+	// Filter non fingerprints action state
+	boolean checked = this.preferences.getBoolean(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS, IPerformancesConstants.DEFAULT_FILTER_ADVANCED_SCENARIOS);
+	this.filterAdvancedScenarios.setChecked(checked);
+}
+
+/*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.part.ViewPart#saveState(org.eclipse.ui.IMemento)
+ */
+public void saveState(IMemento memento) {
+	super.saveState(memento);
+}
+
+/*
+ * (non-Javadoc)
+ * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
+ */
+public void selectionChanged(SelectionChangedEvent event) {
+	final Object[] selection = ((TreeSelection) event.getSelection()).toArray();
+	String firstBuildResults = null;
+	String secondBuildResults = null;
+	if (selection.length == 2) {
+		if (selection[0] instanceof BuildResultsElement) {
+			firstBuildResults = ((BuildResultsElement) selection[0]).getName();
+		}
+		if (selection[1] instanceof BuildResultsElement) {
+			secondBuildResults = ((BuildResultsElement) selection[1]).getName();
+		}
+		if (!firstBuildResults.equals(this.currentBuild) || !secondBuildResults.equals(this.referenceBuild)) {
+			this.currentBuild = firstBuildResults;
+			this.referenceBuild = secondBuildResults;
+			resetTabFolders(true);
+		}
+	}
+}
+
+/*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
+ */
+public void setFocus() {
+	// do nothing
+}
+
+}
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsView.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsView.java
index d703105..f6b9a99 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsView.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/BuildsView.java
@@ -27,6 +27,7 @@
 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
@@ -38,6 +39,7 @@
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.test.internal.performance.results.db.DB_Results;
 import org.eclipse.test.internal.performance.results.model.BuildResultsElement;
 import org.eclipse.test.internal.performance.results.model.ResultsElement;
@@ -274,6 +276,7 @@
 
 	// Views
 	PerformancesView componentsView;
+	BuildsComparisonView buildsComparisonView = null;
 
 	// Results model
 	BuildResultsElement[] buildsResults;
@@ -382,6 +385,16 @@
 	};
 	this.viewer.setSorter(nameSorter);
 
+	// Add results view as listener to viewer selection changes
+	Display.getDefault().asyncExec(new Runnable() {
+		public void run() {
+			ISelectionChangedListener listener = getComparisonView();
+			if (listener != null) {
+				BuildsView.this.viewer.addSelectionChangedListener(listener);
+			}
+		}
+	});
+
 	// Finalize viewer initialization
 	PlatformUI.getWorkbench().getHelpSystem().setHelp(this.viewer.getControl(), "org.eclipse.test.performance.ui.builds");
 	finalizeViewerCreation();
@@ -440,6 +453,16 @@
 }
 
 /*
+ * Return the components results view.
+ */
+BuildsComparisonView getComparisonView() {
+	if (this.buildsComparisonView == null) {
+		this.buildsComparisonView = (BuildsComparisonView) getWorkbenchView("org.eclipse.test.internal.performance.results.ui.BuildsComparisonView");
+	}
+	return this.buildsComparisonView;
+}
+
+/*
  * Return the components view.
  */
 PerformancesView getSiblingView() {
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/ComponentResultsView.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/ComponentResultsView.java
index 158361d..d166c3d 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/ComponentResultsView.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/ComponentResultsView.java
@@ -10,8 +10,6 @@
  *******************************************************************************/
 package org.eclipse.test.internal.performance.results.ui;
 
-import java.util.StringTokenizer;
-
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
 import org.eclipse.core.runtime.preferences.InstanceScope;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
@@ -81,9 +79,6 @@
  */
 public class ComponentResultsView extends ViewPart implements ISelectionChangedListener, IPreferenceChangeListener {
 
-	// Constants
-	private static final String ORG_ECLIPSE = "org.eclipse.";
-
 	// SWT resources
 	CTabFolder tabFolder;
 
@@ -337,7 +332,7 @@
 
 	// Set the part name when possible
 	if (this.componentResultsElement != null) {
-		setPartName();
+		setPartName(Util.componentDisplayName(this.componentResultsElement.getName()));
 	}
 
 	// If this is the first display then look for the first error to set the selection on it
@@ -486,7 +481,7 @@
  * 	- org.eclipse.jdt.ui -> "JDT/UI"
  * 	- org.eclipse.jdt.core -> "JDT/Core"
  * 	- org.eclipse.pde.api.tools -> "PDE/API Tools"
- */
+ *
 protected void setPartName() {
 	String componentName = this.componentResultsElement.getName();
 	String partName;
@@ -527,5 +522,6 @@
 	buffer.append("' results");
 	setPartName(buffer.toString());
 }
+*/
 
 }
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferenceInitializer.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferenceInitializer.java
index 4f27bfe..d40edc3 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferenceInitializer.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferenceInitializer.java
@@ -43,6 +43,11 @@
 	// Status
 	defaultPreferences.putInt(PRE_WRITE_STATUS, IPerformancesConstants.DEFAULT_WRITE_STATUS);
 
+	// Comparison
+	defaultPreferences.putInt(PRE_COMPARISON_THRESHOLD_FAILURE, IPerformancesConstants.DEFAULT_COMPARISON_THRESHOLD_FAILURE);
+	defaultPreferences.putInt(PRE_COMPARISON_THRESHOLD_ERROR, IPerformancesConstants.DEFAULT_COMPARISON_THRESHOLD_ERROR);
+	defaultPreferences.putInt(PRE_COMPARISON_THRESHOLD_IMPROVEMENT, IPerformancesConstants.DEFAULT_COMPARISON_THRESHOLD_IMPROVEMENT);
+
 	// Config descriptors
 	String[][] configDescriptors = PerformanceTestPlugin.getConfigDescriptors();
 	int cdLength = configDescriptors.length;
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferencePage.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferencePage.java
index d5c4b6b..459d1ed 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferencePage.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/ui/PerformanceResultsPreferencePage.java
@@ -82,6 +82,9 @@
 	private Button statusStatisticErraticRadioButton;
 	private Button statusStatisticUnstableRadioButton;
 	private Text statusBuildsToConfirm;
+	private Text comparisonThresholdFailure;
+	private Text comparisonThresholdError;
+	private Text comparisonThresholdImprovement;
 
 	// TODO See whether config descriptors need to be set as preferences or not...
 	// private Table configDescriptorsTable;
@@ -221,6 +224,21 @@
 		this.statusBuildsToConfirm = createTextField(statusGroup);
 		this.statusBuildsToConfirm.setToolTipText("The number of previous builds to take into account to confirm a regression");
 
+		// Comparison
+		Composite compositeComparison = createComposite(parent, 1, 3);
+		Group comparisonGroup = createGroup(compositeComparison, "Comparison", 1);
+		Group thresholdsGroup = createGroup(comparisonGroup, "Thresholds", 6);
+//		Composite compositeFailureThreshold = createComposite(comparisonGroup, 2, 2);
+		createLabel(thresholdsGroup, "Failure:", false);
+		this.comparisonThresholdFailure = createTextField(thresholdsGroup);
+		this.comparisonThresholdFailure.setToolTipText("The threshold in percentage to report a failure");
+		createLabel(thresholdsGroup, "Error:", false);
+		this.comparisonThresholdError = createTextField(thresholdsGroup);
+		this.comparisonThresholdError.setToolTipText("The threshold in percentage to report an error");
+		createLabel(thresholdsGroup, "Improvement:", false);
+		this.comparisonThresholdImprovement = createTextField(thresholdsGroup);
+		this.comparisonThresholdImprovement.setToolTipText("The threshold in percentage to report an improvement");
+
 		// Milestones
 		Composite compositeMilestones = createComposite(parent, 3, 1);
 		createLabel(compositeMilestones, "Milestones", false);
@@ -526,6 +544,11 @@
 	int writeStatus = store.getDefaultInt(PRE_WRITE_STATUS);
 	initStatusValues(writeStatus);
 
+	// Init comparison thresholds
+	this.comparisonThresholdFailure.setText(String.valueOf(store.getDefaultInt(PRE_COMPARISON_THRESHOLD_FAILURE)));
+	this.comparisonThresholdError.setText(String.valueOf(store.getDefaultInt(PRE_COMPARISON_THRESHOLD_ERROR)));
+	this.comparisonThresholdImprovement.setText(String.valueOf(store.getDefaultInt(PRE_COMPARISON_THRESHOLD_IMPROVEMENT)));
+
 	// Init eclipse version
 	this.mVersionRadioButton.setSelection(false);
 	this.dVersionRadionButton.setSelection(false);
@@ -596,6 +619,11 @@
 	int writeStatus = store.getInt(PRE_WRITE_STATUS);
 	initStatusValues(writeStatus);
 
+	// Init comparison thresholds
+	this.comparisonThresholdFailure.setText(String.valueOf(store.getInt(PRE_COMPARISON_THRESHOLD_FAILURE)));
+	this.comparisonThresholdError.setText(String.valueOf(store.getInt(PRE_COMPARISON_THRESHOLD_ERROR)));
+	this.comparisonThresholdImprovement.setText(String.valueOf(store.getInt(PRE_COMPARISON_THRESHOLD_IMPROVEMENT)));
+
 	// Init eclipse version
 	int version = store.getInt(PRE_ECLIPSE_VERSION);
 	if (version == ECLIPSE_MAINTENANCE_VERSION) {
@@ -1004,6 +1032,11 @@
 	writeStatus += Integer.parseInt(this.statusBuildsToConfirm.getText());
 	store.setValue(PRE_WRITE_STATUS, writeStatus);
 
+	// Init comparison thresholds
+	store.setValue(PRE_COMPARISON_THRESHOLD_FAILURE, Integer.parseInt(this.comparisonThresholdFailure.getText()));
+	store.setValue(PRE_COMPARISON_THRESHOLD_ERROR, Integer.parseInt(this.comparisonThresholdError.getText()));
+	store.setValue(PRE_COMPARISON_THRESHOLD_IMPROVEMENT, Integer.parseInt(this.comparisonThresholdImprovement.getText()));
+
 	// Set milestones
 	String prefix = PRE_MILESTONE_BUILDS + "." + version;
 	count  = this.milestonesCombo.getItemCount();
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/IPerformancesConstants.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/IPerformancesConstants.java
index 0dc421b..b1bd2b1 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/IPerformancesConstants.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/IPerformancesConstants.java
@@ -76,6 +76,14 @@
 	public static final int STATUS_STATISTICS_MASK = 0xC000;
 	public static final int DEFAULT_WRITE_STATUS = STATUS_ERROR_NONE | DEFAULT_BUILDS_NUMBER;
 
+	// Comparison
+    public static final String PRE_COMPARISON_THRESHOLD_FAILURE = PREFIX + "comparison.threshold.failure"; //$NON-NLS-1$
+	public static final int DEFAULT_COMPARISON_THRESHOLD_FAILURE = 10;
+    public static final String PRE_COMPARISON_THRESHOLD_ERROR = PREFIX + "comparison.threshold.error"; //$NON-NLS-1$
+	public static final int DEFAULT_COMPARISON_THRESHOLD_ERROR = 3;
+    public static final String PRE_COMPARISON_THRESHOLD_IMPROVEMENT = PREFIX + "comparison.threshold.imporvement"; //$NON-NLS-1$
+	public static final int DEFAULT_COMPARISON_THRESHOLD_IMPROVEMENT = 10;
+
 	// Default milestones nowadays
 	public static final String[] V37_MILESTONES = new String[] {
 	};
diff --git a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/Util.java b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/Util.java
index 185b4d7..4bddd41 100644
--- a/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/Util.java
+++ b/bundles/org.eclipse.test.performance.ui/src/org/eclipse/test/internal/performance/results/utils/Util.java
@@ -78,6 +78,9 @@
 	private static String[] MILESTONES;
 	public static final BuildDateComparator BUILD_DATE_COMPARATOR = new BuildDateComparator();
 
+	// Components constants
+	public static final String ORG_ECLIPSE = "org.eclipse.";
+
 static class BuildDateComparator implements Comparator {
 	public int compare(Object o1, Object o2) {
 		String s1 = (String) o1;
@@ -120,6 +123,84 @@
 public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); //$NON-NLS-1$
 
 /**
+ * Return the name to display for the given component.
+ * <p>
+ * This name is built from the name of the component selected in the Components view.
+ * The rules to build the name are:
+ * <ol>
+ * <li>If the component name does not start with "org.eclipse" then the part name is just
+ * 		"<b>component name</b>"</li>
+ * <li>Otherwise, remove "org.eclipse." form the component name and count
+ * 		the tokens separated by a dot ('.')
+ * 		<ul>
+ * 		<li>If there's only one remaining token, then the part name is "<b>Platform/</b>"
+ * 			followed by:
+ * 			<ul>
+ * 			<li>"<b><i>TOKEN</i></b>" if token is less than 3 characters,</li>
+ * 			<li>"<b><i>Token</i></b>" otherwise</li>
+ * 			</ul>
+ * 		</li>
+ * 		<li>Otherwise then the part name is "<b><i>FIRST_TOKEN</i></b>" followed by:
+ * 			<ul>
+ * 			<li>for each followed additional token:
+ * 				<ul>
+ * 				<li>"<b><i>TOKEN</i></b>" if token is less than 3 characters,</li>
+ * 				<li>"<b><i>Token</i></b>" otherwise</li>
+ * 				</ul>
+ * 			</li>
+ * 			</ul>
+ * 		</li>
+ * 		</ul>
+ * </ol>
+ * E.g.
+ * 	- org.eclipse.ui -> "Platform/UI"
+ * 	- org.eclipse.swt -> "Platform/SWT"
+ * 	- org.eclipse.team -> "Platform/Team"
+ * 	- org.eclipse.jdt.ui -> "JDT/UI"
+ * 	- org.eclipse.jdt.core -> "JDT/Core"
+ * 	- org.eclipse.pde.api.tools -> "PDE/API Tools"
+ */
+public static String componentDisplayName(String componentName) {
+	String partName;
+	StringBuffer buffer = null;
+	if (componentName.startsWith(ORG_ECLIPSE)) {
+		partName = componentName.substring(ORG_ECLIPSE.length());
+		StringTokenizer tokenizer = new StringTokenizer(partName, ".");
+		while (tokenizer.hasMoreTokens()) {
+			String token = tokenizer.nextToken();
+			if (buffer == null) {
+				if (tokenizer.hasMoreTokens()) {
+					buffer = new StringBuffer("'"+token.toUpperCase());
+					buffer.append('/');
+				} else {
+					buffer = new StringBuffer("'Platform/");
+					if (token.length() > 3) {
+						buffer.append(Character.toUpperCase(token.charAt(0)));
+						buffer.append(token.substring(1));
+					} else {
+						buffer.append(token.toUpperCase());
+					}
+				}
+			} else {
+				if (token.length() > 3) {
+					buffer.append(Character.toUpperCase(token.charAt(0)));
+					buffer.append(token.substring(1));
+				} else {
+					buffer.append(token.toUpperCase());
+				}
+				if (tokenizer.hasMoreTokens()) buffer.append(' ');
+			}
+		}
+	} else {
+		buffer = new StringBuffer("'");
+		buffer.append(componentName);
+		buffer.append("'");
+	}
+	buffer.append("' results");
+	return buffer.toString();
+}
+
+/**
  * Compute the student t-test values.
  *
  * @see "http://en.wikipedia.org/wiki/Student's_t-test"