• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
ConfigTab(String name, String box)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  */
createTabFolderPage(ComponentResultsElement componentResultsElement, CTabFolder tabFolder, boolean fullSelection)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  */
createToolTip(String toolTipText, String toolTipMessage, int toolTipStyle, Point position)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  */
currentCellPosition(int x, int y)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  */
dispose()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  */
disposeTable()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  */
fillTableLines(boolean fingerprints)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  */
getLayoutDataFieldNames(boolean fingerprints)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  */
getTabText()542 public String getTabText() {
543 	return this.configBox;
544 }
545 
546 /*
547  * Init the SWT resources
548  */
initResources()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  */
select(BuildResultsElement buildResultsElement)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