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