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
14 import java.io.File;
15 import java.io.PrintWriter;
16 import java.io.StringWriter;
17 import java.lang.reflect.InvocationTargetException;
18 import java.util.Comparator;
19
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.core.runtime.IStatus;
22 import org.eclipse.core.runtime.preferences.InstanceScope;
23 import org.eclipse.jface.action.Action;
24 import org.eclipse.jface.action.IMenuManager;
25 import org.eclipse.jface.action.Separator;
26 import org.eclipse.jface.dialogs.MessageDialog;
27 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
28 import org.eclipse.jface.operation.IRunnableWithProgress;
29 import org.eclipse.jface.resource.JFaceResources;
30 import org.eclipse.jface.viewers.IStructuredSelection;
31 import org.eclipse.jface.viewers.LabelProvider;
32 import org.eclipse.jface.viewers.SelectionChangedEvent;
33 import org.eclipse.jface.viewers.TreeViewer;
34 import org.eclipse.jface.viewers.Viewer;
35 import org.eclipse.jface.viewers.ViewerSorter;
36 import org.eclipse.swt.SWT;
37 import org.eclipse.swt.graphics.Color;
38 import org.eclipse.swt.graphics.Font;
39 import org.eclipse.swt.graphics.FontData;
40 import org.eclipse.swt.widgets.Composite;
41 import org.eclipse.test.internal.performance.results.db.DB_Results;
42 import org.eclipse.test.internal.performance.results.model.BuildResultsElement;
43 import org.eclipse.test.internal.performance.results.model.ResultsElement;
44 import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
45 import org.eclipse.test.internal.performance.results.utils.Util;
46 import org.eclipse.test.performance.ui.GenerateResults;
47 import org.eclipse.ui.PlatformUI;
48 import org.eclipse.ui.dialogs.ElementListSelectionDialog;
49 import org.eclipse.ui.model.WorkbenchContentProvider;
50 import 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 */
60 public class BuildsView extends PerformancesView {
61
62 /**
63 * Action to generate results.
64 */
65 final class GenerateAction extends Action {
66 IStatus status;
67
run()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 */
generate(int i, final String baselineName, final boolean fingerprints)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
UpdateBuildAction(boolean force)194 UpdateBuildAction(boolean force) {
195 super();
196 this.force = force;
197 }
198
run()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
updateBuilds(IProgressMonitor monitor)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
UpdateAllBuildsAction(boolean force)248 UpdateAllBuildsAction(boolean force) {
249 super(force);
250 }
251 //
252 // public boolean isEnabled() {
253 // String[] elements = buildsToUpdate();
254 // return elements != null;
255 // }
256
updateBuilds(IProgressMonitor monitor)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 {
compare(Object o1, Object o2)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 */
BuildsView()295 public 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 */
buildsToUpdate()303 String[] 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 */
createPartControl(Composite parent)324 public 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 */
dispose()392 public 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 */
fillContextMenu(IMenuManager manager)403 void 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 */
fillFiltersDropDown(IMenuManager manager)414 void fillFiltersDropDown(IMenuManager manager) {
415 super.fillFiltersDropDown(manager);
416 manager.add(this.filterLastBuilds);
417 }
418
419 /*
420 * Fill the local data drop-down menu
421 */
fillLocalDataDropDown(IMenuManager manager)422 void 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 */
getBuilds()432 Object[] getBuilds() {
433 if (this.results == null) {
434 initResults();
435 }
436 return this.results.getBuilds();
437 }
438
439 /*
440 * Return the components view.
441 */
getSiblingView()442 PerformancesView 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 */
makeActions()453 void 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 */
resetView()484 public 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 */
selectionChanged(SelectionChangedEvent event)582 public 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
635 void 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
647 void 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 }