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 *******************************************************************************/ 11package org.eclipse.test.internal.performance.results.ui; 12 13 14import java.io.File; 15import java.io.PrintWriter; 16import java.io.StringWriter; 17import java.lang.reflect.InvocationTargetException; 18import java.util.Comparator; 19 20import org.eclipse.core.runtime.IProgressMonitor; 21import org.eclipse.core.runtime.IStatus; 22import org.eclipse.core.runtime.preferences.InstanceScope; 23import org.eclipse.jface.action.Action; 24import org.eclipse.jface.action.IMenuManager; 25import org.eclipse.jface.action.Separator; 26import org.eclipse.jface.dialogs.MessageDialog; 27import org.eclipse.jface.dialogs.ProgressMonitorDialog; 28import org.eclipse.jface.operation.IRunnableWithProgress; 29import org.eclipse.jface.resource.JFaceResources; 30import org.eclipse.jface.viewers.IStructuredSelection; 31import org.eclipse.jface.viewers.LabelProvider; 32import org.eclipse.jface.viewers.SelectionChangedEvent; 33import org.eclipse.jface.viewers.TreeViewer; 34import org.eclipse.jface.viewers.Viewer; 35import org.eclipse.jface.viewers.ViewerSorter; 36import org.eclipse.swt.SWT; 37import org.eclipse.swt.graphics.Color; 38import org.eclipse.swt.graphics.Font; 39import org.eclipse.swt.graphics.FontData; 40import org.eclipse.swt.widgets.Composite; 41import org.eclipse.test.internal.performance.results.db.DB_Results; 42import org.eclipse.test.internal.performance.results.model.BuildResultsElement; 43import org.eclipse.test.internal.performance.results.model.ResultsElement; 44import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants; 45import org.eclipse.test.internal.performance.results.utils.Util; 46import org.eclipse.test.performance.ui.GenerateResults; 47import org.eclipse.ui.PlatformUI; 48import org.eclipse.ui.dialogs.ElementListSelectionDialog; 49import org.eclipse.ui.model.WorkbenchContentProvider; 50import org.eclipse.ui.model.WorkbenchLabelProvider; 51 52 53/** 54 * View to see all the builds which have performance results stored in the database. 55 * <p> 56 * Typical actions from this view are update local data files with builds results 57 * and generated the HTML pages. 58 * </p> 59 */ 60public class BuildsView extends PerformancesView { 61 62 /** 63 * Action to generate results. 64 */ 65 final class GenerateAction extends Action { 66 IStatus status; 67 68 public void run() { 69 70 // Ask for output directory 71 String resultGenerationDir = BuildsView.this.preferences.get(IPerformancesConstants.PRE_RESULTS_GENERATION_DIR, ""); 72 String pathFilter = (BuildsView.this.outputDir == null) ? resultGenerationDir : BuildsView.this.outputDir.getPath(); 73 File dir = changeDir(pathFilter, "Select directory to write comparison files"); 74 if (dir == null) { 75 return; 76 } 77 BuildsView.this.outputDir = dir; 78 BuildsView.this.preferences.put(IPerformancesConstants.PRE_RESULTS_GENERATION_DIR, dir.getAbsolutePath()); 79 80 // Select the reference 81 String[] baselines = BuildsView.this.results.getBaselines(); 82 int bLength = baselines.length; 83 String selectedBaseline; 84 switch (bLength) { 85 case 0: 86 // no baseline, nothing to do... 87 selectedBaseline = BuildsView.this.results.getPerformanceResults().getBaselineName(); 88 break; 89 case 1: 90 // only one baseline, no selection to do 91 selectedBaseline = baselines[0]; 92 break; 93 default: 94 // select the baseline from list 95 ElementListSelectionDialog dialog = new ElementListSelectionDialog(getSite().getShell(), new LabelProvider()); 96 dialog.setTitle(getTitleToolTip()); 97 dialog.setMessage("Select the baseline to use while generating results:"); 98 String[] defaultBaseline = new String[] { baselines[baselines.length - 1] }; 99 dialog.setInitialSelections(defaultBaseline); 100 dialog.setElements(baselines); 101 dialog.open(); 102 Object[] selected = dialog.getResult(); 103 if (selected == null) 104 return; 105 selectedBaseline = (String) selected[0]; 106 break; 107 } 108 final String baselineName = selectedBaseline; 109 BuildsView.this.results.getPerformanceResults().setBaselineName(baselineName); 110 111 // Ask for fingerprints 112 final boolean fingerprints = MessageDialog.openQuestion(BuildsView.this.shell, getTitleToolTip(), "Generate only fingerprints?"); 113 114 // Generate all selected builds 115 int length = BuildsView.this.buildsResults.length; 116 for (int i = 0; i < length; i++) { 117 generate(i, baselineName, fingerprints); 118 } 119 } 120 121 /* 122 * Generate the HTML pages. 123 */ 124 private void generate(int i, final String baselineName, final boolean fingerprints) { 125 // Create output directory 126 final String buildName = BuildsView.this.buildsResults[i].getName(); 127 final File genDir = new File(BuildsView.this.outputDir, buildName); 128 if (!genDir.exists() && !genDir.mkdir()) { 129 MessageDialog.openError(BuildsView.this.shell, getTitleToolTip(), "Cannot create " + genDir.getPath() + " to generate results!"); 130 return; 131 } 132 133 // Create runnable 134 IRunnableWithProgress runnable = new IRunnableWithProgress() { 135 136 public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { 137 try { 138 monitor.beginTask("Generate performance results", 10000); 139 GenerateResults generation = new GenerateResults(BuildsView.this.results.getPerformanceResults(), 140 buildName, 141 baselineName, 142 fingerprints, 143 BuildsView.this.dataDir, 144 genDir); 145 GenerateAction.this.status = generation.run(monitor); 146 monitor.done(); 147 } catch (Exception e) { 148 e.printStackTrace(); 149 } 150 } 151 }; 152 153 // Run with progress monitor 154 ProgressMonitorDialog readProgress = new ProgressMonitorDialog(getSite().getShell()); 155 try { 156 readProgress.run(true, true, runnable); 157 } catch (InvocationTargetException e) { 158 // skip 159 } catch (InterruptedException e) { 160 // skip 161 } 162 163 // Results 164 if (!this.status.isOK()) { 165 StringWriter swriter = new StringWriter(); 166 PrintWriter pwriter = new PrintWriter(swriter); 167 swriter.write(this.status.getMessage()); 168 Throwable ex = this.status.getException(); 169 if (ex != null) { 170 swriter.write(": "); 171 swriter.write(ex.getMessage()); 172 swriter.write('\n'); 173 ex.printStackTrace(pwriter); 174 } 175 MessageDialog.open(this.status.getSeverity(), 176 BuildsView.this.shell, 177 getTitleToolTip(), 178 swriter.toString(), 179 SWT.NONE); 180 } 181 } 182 } 183 184 /** 185 * Action to update local data files with the performance results of a build. 186 * 187 * This may be done lazily (i.e. not done if the local data already knows 188 * the build) or forced (i.e. done whatever the local data files contain). 189 */ 190 class UpdateBuildAction extends Action { 191 192 boolean force; 193 194 UpdateBuildAction(boolean force) { 195 super(); 196 this.force = force; 197 } 198 199 public void run() { 200 201 // Verify that directories are set 202 if (BuildsView.this.dataDir == null) { 203 if (changeDataDir() == null) { 204 if (!MessageDialog.openConfirm(BuildsView.this.shell, getTitleToolTip(), "No local files directory is set, hence the update could not be written! OK to continue?")) { 205 return; 206 } 207 } 208 } 209 210 // Progress dialog 211 IRunnableWithProgress runnable = new IRunnableWithProgress() { 212 213 public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { 214 try { 215 updateBuilds(monitor); 216 } catch (Exception e) { 217 e.printStackTrace(); 218 } 219 } 220 }; 221 ProgressMonitorDialog readProgress = new ProgressMonitorDialog(getSite().getShell()); 222 try { 223 readProgress.run(true, true, runnable); 224 } catch (InvocationTargetException e) { 225 return; 226 } catch (InterruptedException e) { 227 return; 228 } 229 230 // Reset Components and Builds views input 231 refreshInput(); 232 getSiblingView().refreshInput(); 233 } 234 235 void updateBuilds(IProgressMonitor monitor) { 236 BuildsView.this.updateBuilds(monitor, this.force); 237 } 238 } 239 240 /** 241 * Action to update local data files with the performance results of all builds. 242 * 243 * This may be done lazily (i.e. not done if the local data already knows 244 * the build) or forced (i.e. done whatever the local data files contain). 245 */ 246 class UpdateAllBuildsAction extends UpdateBuildAction { 247 248 UpdateAllBuildsAction(boolean force) { 249 super(force); 250 } 251// 252// public boolean isEnabled() { 253// String[] elements = buildsToUpdate(); 254// return elements != null; 255// } 256 257 void updateBuilds(IProgressMonitor monitor) { 258 BuildsView.this.updateAllBuilds(monitor, this.force); 259 } 260 } 261 262 /** 263 * Class to compare builds regarding their date instead of their name. 264 * 265 * @see Util#getBuildDate(String) 266 */ 267 class BuildDateComparator implements Comparator { 268 public int compare(Object o1, Object o2) { 269 String s1 = (String) o1; 270 String s2 = (String) o2; 271 return Util.getBuildDate(s1).compareTo(Util.getBuildDate(s2)); 272 } 273 } 274 275 // Views 276 PerformancesView componentsView; 277 278 // Results model 279 BuildResultsElement[] buildsResults; 280 281 // Generation info 282 File outputDir; 283 284 // Actions 285 Action generate; 286 UpdateBuildAction updateBuild, updateAllBuilds; 287// UpdateBuildAction forceUpdateBuild, forceUpdateAllBuilds; 288 289 // SWT resources 290 Font italicFont; 291 292/* 293 * Default constructor. 294 */ 295public BuildsView() { 296 this.preferences = new InstanceScope().getNode(IPerformancesConstants.PLUGIN_ID); 297 this.preferences.addPreferenceChangeListener(this); 298} 299 300/* 301 * Compute the list of builds to update based on their status. 302 */ 303String[] buildsToUpdate() { 304 Object[] elements = this.results.getBuilds(); 305 int length = elements.length; 306 String[] buildsToUpdate = new String[length]; 307 int count = 0; 308 for (int i=0; i<length; i++) { 309 BuildResultsElement element = (BuildResultsElement) elements[i]; 310 if (element.getStatus() == 0) { 311 buildsToUpdate[count++] = element.getName(); 312 } 313 } 314 if (count == 0) return null; 315 if (count < length) { 316 System.arraycopy(buildsToUpdate, 0, buildsToUpdate = new String[count], 0, count); 317 } 318 return buildsToUpdate; 319} 320 321/* (non-Javadoc) 322 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#createPartControl(org.eclipse.swt.widgets.Composite) 323 */ 324public void createPartControl(Composite parent) { 325 super.createPartControl(parent); 326 327 // Create the viewer 328 this.viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); 329 330 // Set the content provider: first level is builds list 331 WorkbenchContentProvider contentProvider = new WorkbenchContentProvider() { 332 public Object[] getElements(Object o) { 333 return getBuilds(); 334 } 335 }; 336 this.viewer.setContentProvider(contentProvider); 337 338 // Set the label provider 339 WorkbenchLabelProvider labelProvider = new WorkbenchLabelProvider() { 340 341 // Set an italic font when no local data have been read 342 public Font getFont(Object element) { 343 Font font = super.getFont(element); 344 if (element instanceof BuildResultsElement) { 345 if (((BuildResultsElement) element).isUnknown()) { 346 if (BuildsView.this.italicFont == null) { 347 FontData[] defaultFont = JFaceResources.getDefaultFont().getFontData(); 348 FontData italicFontData = new FontData(defaultFont[0].getName(), defaultFont[0].getHeight(), SWT.ITALIC); 349 BuildsView.this.italicFont = new Font(DEFAULT_DISPLAY, italicFontData); 350 } 351 return BuildsView.this.italicFont; 352 } 353 } 354 return font; 355 } 356 357 // Set font in gray when no local data is available (i.e. local data needs to be updated) 358 public Color getForeground(Object element) { 359 Color color = super.getForeground(element); 360 if (element instanceof BuildResultsElement) { 361 if (!((BuildResultsElement) element).isRead()) { 362 color = DARK_GRAY; 363 } 364 } 365 return color; 366 } 367 }; 368 this.viewer.setLabelProvider(labelProvider); 369 370 // Set the children sorter 371 ViewerSorter nameSorter = new ViewerSorter() { 372 373 // Sort children using specific comparison (see the implementation 374 // of the #compareTo(Object) in the ResultsElement hierarchy 375 public int compare(Viewer view, Object e1, Object e2) { 376 if (e2 instanceof ResultsElement) { 377 return ((ResultsElement) e2).compareTo(e1); 378 } 379 return super.compare(view, e1, e2); 380 } 381 }; 382 this.viewer.setSorter(nameSorter); 383 384 // Finalize viewer initialization 385 PlatformUI.getWorkbench().getHelpSystem().setHelp(this.viewer.getControl(), "org.eclipse.test.performance.ui.builds"); 386 finalizeViewerCreation(); 387} 388 389/* (non-Javadoc) 390 * @see org.eclipse.ui.part.WorkbenchPart#dispose() 391 */ 392public void dispose() { 393 if (this.italicFont != null) { 394 this.italicFont.dispose(); 395 } 396 super.dispose(); 397} 398 399/* 400 * (non-Javadoc) 401 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#fillContextMenu(org.eclipse.jface.action.IMenuManager) 402 */ 403void fillContextMenu(IMenuManager manager) { 404 super.fillContextMenu(manager); 405 manager.add(this.generate); 406 manager.add(this.updateBuild); 407// manager.add(this.forceUpdateBuild); 408} 409 410/* 411 * (non-Javadoc) 412 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#fillLocalPullDown(org.eclipse.jface.action.IMenuManager) 413 */ 414void fillFiltersDropDown(IMenuManager manager) { 415 super.fillFiltersDropDown(manager); 416 manager.add(this.filterLastBuilds); 417} 418 419/* 420 * Fill the local data drop-down menu 421 */ 422void fillLocalDataDropDown(IMenuManager manager) { 423 super.fillLocalDataDropDown(manager); 424 manager.add(new Separator()); 425 manager.add(this.updateAllBuilds); 426// manager.add(this.forceUpdateAllBuilds); 427} 428 429/* 430 * Get all builds from the model. 431 */ 432Object[] getBuilds() { 433 if (this.results == null) { 434 initResults(); 435 } 436 return this.results.getBuilds(); 437} 438 439/* 440 * Return the components view. 441 */ 442PerformancesView getSiblingView() { 443 if (this.componentsView == null) { 444 this.componentsView = (PerformancesView) getWorkbenchView("org.eclipse.test.internal.performance.results.ui.ComponentsView"); 445 } 446 return this.componentsView; 447} 448 449/* 450 * (non-Javadoc) 451 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#makeActions() 452 */ 453void makeActions() { 454 455 super.makeActions(); 456 457 // Generate action 458 this.generate = new GenerateAction(); 459 this.generate.setText("&Generate"); 460 461 // Update build actions 462 boolean connected = this.preferences.getBoolean(IPerformancesConstants.PRE_DATABASE_CONNECTION, IPerformancesConstants.DEFAULT_DATABASE_CONNECTION); 463 this.updateBuild = new UpdateBuildAction(false); 464 this.updateBuild.setText("&Update from DB"); 465 this.updateBuild.setEnabled(connected); 466// this.forceUpdateBuild = new UpdateBuildAction(true); 467// this.forceUpdateBuild.setText("Force Update"); 468 469 // Update build action 470 this.updateAllBuilds = new UpdateAllBuildsAction(false); 471 this.updateAllBuilds.setText("&Update from DB (all)"); 472 this.updateAllBuilds.setEnabled(connected); 473// this.forceUpdateAllBuilds = new UpdateAllBuildsAction(true); 474// this.forceUpdateAllBuilds.setText("Force Update all"); 475 476 // Set filters default 477 this.filterBaselineBuilds.setChecked(false); 478 this.filterNightlyBuilds.setChecked(false); 479} 480 481/** 482 * Reset the views. 483 */ 484public void resetView() { 485 486 boolean debug = true; 487 488 // Look whether database constants has changed or not 489 int eclipseVersion = this.preferences.getInt(IPerformancesConstants.PRE_ECLIPSE_VERSION, IPerformancesConstants.DEFAULT_ECLIPSE_VERSION); 490 boolean connected = this.preferences.getBoolean(IPerformancesConstants.PRE_DATABASE_CONNECTION, IPerformancesConstants.DEFAULT_DATABASE_CONNECTION); 491 String databaseLocation = this.preferences.get(IPerformancesConstants.PRE_DATABASE_LOCATION, IPerformancesConstants.NETWORK_DATABASE_LOCATION); 492 String lastBuild = this.preferences.get(IPerformancesConstants.PRE_LAST_BUILD, null); 493 boolean noLastBuild = lastBuild.length() == 0; 494 if (debug) { 495 System.out.println("Reset View:"); 496 System.out.println(" - eclispe version = "+eclipseVersion); 497 System.out.println(" - connected = "+connected); 498 System.out.println(" - db location = "+databaseLocation); 499 System.out.println(" - last build = "+(noLastBuild?"<none>":lastBuild)); 500 } 501 final boolean sameVersion = DB_Results.getDbVersion().endsWith(Integer.toString(eclipseVersion)); 502 final boolean sameConnection = connected == DB_Results.DB_CONNECTION; 503 final boolean sameDB = sameVersion && databaseLocation.equals(DB_Results.getDbLocation()); 504 boolean sameLastBuild = (noLastBuild && LAST_BUILD == null) || lastBuild.equals(LAST_BUILD); 505 if (debug) { 506 System.out.println(" - same version: "+sameVersion); 507 System.out.println(" - same connection: "+sameConnection); 508 System.out.println(" - same DB: "+sameDB); 509 System.out.println(" - same last build: "+sameLastBuild); 510 } 511 final PerformancesView siblingView = getSiblingView(); 512 if (sameConnection && sameDB) { 513 if (!sameLastBuild) { 514 // Set last build 515 LAST_BUILD = noLastBuild ? null : lastBuild; 516 this.results.setLastBuildName(LAST_BUILD); 517 siblingView.results.setLastBuildName(LAST_BUILD); 518 519 // Reset views content 520 resetInput(); 521 siblingView.resetInput(); 522 523 // May be read local data now 524 File newDataDir = changeDataDir(); 525 if (newDataDir == null) { 526 this.dataDir = null; 527 siblingView.dataDir = null; 528 } 529 } 530 // No database preferences has changed do nothing 531 return; 532 } 533 534 // Update database constants 535 boolean updated = DB_Results.updateDbConstants(connected, eclipseVersion, databaseLocation); 536 if (debug) { 537 System.out.println(" - updated: "+updated); 538 } 539 if (!connected) { 540 if (!updated) { 541 MessageDialog.openError(this.shell, getTitleToolTip(), "Error while updating database results constants!\nOpen error log to see more details on this error"); 542 } 543 } else if (updated) { 544 StringBuffer message = new StringBuffer("Database connection has been correctly "); 545 message.append( connected ? "opened." : "closed."); 546 MessageDialog.openInformation(this.shell, getTitleToolTip(), message.toString()); 547 } else { 548 MessageDialog.openError(this.shell, getTitleToolTip(), "The database connection cannot be established!\nOpen error log to see more details on this error"); 549 DB_Results.updateDbConstants(false, eclipseVersion, databaseLocation); 550 } 551 setTitleToolTip(); 552 siblingView.setTitleToolTip(); 553 554 // Refresh view 555 if (sameVersion && sameLastBuild) { 556 // Refresh only builds view as the sibling view (Components) contents is based on local data files contents 557 this.results.resetBuildNames(); 558 refreshInput(); 559 } else { 560 // Reset views content 561 resetInput(); 562 siblingView.resetInput(); 563 564 // May be read local data now 565 if (MessageDialog.openQuestion(this.shell, getTitleToolTip(), "Do you want to read local data right now?")) { 566 changeDataDir(); 567 } else { 568 this.dataDir = null; 569 siblingView.dataDir = null; 570 } 571 } 572 573 // Update actions 574 this.updateBuild.setEnabled(connected); 575 this.updateAllBuilds.setEnabled(connected); 576} 577 578/* 579 * (non-Javadoc) 580 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent) 581 */ 582public void selectionChanged(SelectionChangedEvent event) { 583 super.selectionChanged(event); 584 585 // Update selected element 586 Object selection = this.viewer.getSelection(); 587 int length = 0; 588 if (selection instanceof IStructuredSelection) { 589 Object[] elements = ((IStructuredSelection)selection).toArray(); 590 length = elements == null ? 0 : elements.length; 591 this.buildsResults = new BuildResultsElement[length]; 592 if (length == 0) { 593 this.updateAllBuilds.setText("&Update from DB (all)"); 594 return; 595 } 596 for (int i=0; i<length; i++) { 597 this.buildsResults[i] = (BuildResultsElement) elements[i]; 598 } 599 } else { 600 return; 601 } 602 603 // Update update build action 604// boolean enableUpdateBuild = true; 605// boolean enableGenerate = true; 606 int readBuilds = 0; 607 for (int i=0; i<length; i++) { 608 if (this.buildsResults[i].isRead()) { 609// enableUpdateBuild = false; 610 readBuilds++; 611 } else { 612// enableGenerate = false; 613 } 614 } 615// this.updateBuild.setEnabled(enableUpdateBuild); 616// this.forceUpdateBuild.setEnabled(!enableUpdateBuild); 617 final boolean force = readBuilds < length; 618 this.updateBuild.force = force; 619 this.updateAllBuilds.force = force; 620 this.updateAllBuilds.setText("&Update from DB"); 621 622 // Update generate action 623 boolean enableGenerate = true; 624 if (enableGenerate) { 625 for (int i=0; i<length; i++) { 626 if (this.buildsResults[i].getName().startsWith(DB_Results.getDbBaselinePrefix())) { 627 enableGenerate = false; 628 break; 629 } 630 } 631 } 632 this.generate.setEnabled(enableGenerate); 633} 634 635void updateAllBuilds(IProgressMonitor monitor, boolean force) { 636 if (this.dataDir == null) { 637 changeDataDir(); 638 } 639 String[] builds = buildsToUpdate(); 640 if (builds == null) { 641 this.results.updateBuild(null, true, this.dataDir, monitor); 642 } else { 643 this.results.updateBuilds(builds, force, this.dataDir, monitor); 644 } 645} 646 647void updateBuilds(IProgressMonitor monitor, boolean force) { 648 if (this.dataDir == null) { 649 changeDataDir(); 650 } 651 int length = this.buildsResults.length; 652 String[] builds = new String[length]; 653 for (int i = 0; i < length; i++) { 654 builds[i] = this.buildsResults[i].getName(); 655 } 656 this.results.updateBuilds(builds, force, this.dataDir, monitor); 657} 658 659}