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