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
13import java.io.BufferedOutputStream;
14import java.io.DataOutputStream;
15import java.io.File;
16import java.io.FileNotFoundException;
17import java.io.FileOutputStream;
18import java.io.IOException;
19import java.util.HashSet;
20import java.util.Iterator;
21import java.util.Set;
22
23import org.eclipse.core.runtime.preferences.InstanceScope;
24import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
25import org.eclipse.jface.action.Action;
26import org.eclipse.jface.action.IAction;
27import org.eclipse.jface.action.IMenuManager;
28import org.eclipse.jface.action.Separator;
29import org.eclipse.jface.dialogs.MessageDialog;
30import org.eclipse.jface.resource.JFaceResources;
31import org.eclipse.jface.viewers.AbstractTreeViewer;
32import org.eclipse.jface.viewers.ISelectionChangedListener;
33import org.eclipse.jface.viewers.SelectionChangedEvent;
34import org.eclipse.jface.viewers.StructuredSelection;
35import org.eclipse.jface.viewers.TreeViewer;
36import org.eclipse.jface.viewers.Viewer;
37import org.eclipse.jface.viewers.ViewerFilter;
38import org.eclipse.jface.viewers.ViewerSorter;
39import org.eclipse.swt.SWT;
40import org.eclipse.swt.graphics.Font;
41import org.eclipse.swt.graphics.FontData;
42import org.eclipse.swt.widgets.Composite;
43import org.eclipse.swt.widgets.Display;
44import org.eclipse.test.internal.performance.results.model.BuildResultsElement;
45import org.eclipse.test.internal.performance.results.model.ComponentResultsElement;
46import org.eclipse.test.internal.performance.results.model.ConfigResultsElement;
47import org.eclipse.test.internal.performance.results.model.ResultsElement;
48import org.eclipse.test.internal.performance.results.model.ScenarioResultsElement;
49import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
50import org.eclipse.test.internal.performance.results.utils.Util;
51import org.eclipse.ui.IMemento;
52import org.eclipse.ui.PlatformUI;
53import org.eclipse.ui.model.WorkbenchContentProvider;
54import org.eclipse.ui.model.WorkbenchLabelProvider;
55
56/**
57 * View to see the performance results of all the components in a hierarchical tree.
58 * <p>
59 * A component defines several performance scenarios which are run on several
60 * machines (aka config). All builds results are stored onto each configuration
61 * and 2 dimensions have been stored for each result: the "Elapsed Process Time"
62 * and the "CPU Time".
63 * </p><p>
64 * There's only one available action from this view: read the local data files. This
65 * populates the hierarchy with the numbers stored in these files.
66 * </p><p>
67 * There's also the possibility to filter the results:
68 * 	<ul>
69 *	<li>Filter for builds:
70 *		<ul>
71 *		<li>Filter baselines:	hide the baselines (starting with R-3.x)</li>
72 *		<li>Filter nightly:	hide the nightly builds (starting with 'N')</li>
73 *		<li>Filter non-important builds:	hide all non-important builds, which means non-milestone builds and those after the last milestone</li>
74 *		</ul>
75 *	</li>
76 *	</li>Filter for scenarios:
77 *		<ul>
78 *		<li>Filter non-fingerprints: hide the scenarios which are not in the fingerprints</li>
79 *		</ul>
80 *	</li>
81 *	</ul>
82 * </p>
83 * @see ComponentResultsView
84 */
85public class ComponentsView extends PerformancesView {
86
87	// Viewer filters
88	final static ViewerFilter FILTER_ADVANCED_SCENARIOS = new ViewerFilter() {
89		public boolean select(Viewer v, Object parentElement, Object element) {
90			if (element instanceof ScenarioResultsElement) {
91				ScenarioResultsElement scenarioElement = (ScenarioResultsElement) element;
92				return scenarioElement.hasSummary();
93			}
94	        return true;
95        }
96	};
97
98	// Views
99	PerformancesView buildsView;
100	ComponentResultsView componentResultsView = null;
101
102	// Internal
103	Set expandedComponents = new HashSet();
104	File resultsDir = null;
105
106	// Actions
107	Action filterAdvancedScenarios;
108	Action writeStatus;
109
110	// SWT resources
111	Font boldFont;
112
113	// Write Status
114	static int WRITE_STATUS;
115
116/**
117 * Default constructor.
118 */
119public ComponentsView() {
120//	this.onlyFingerprintsImageDescriptor = ImageDescriptor.createFromFile(getClass(), "filter_ps.gif");
121	super();
122
123	// Get preferences
124	this.preferences = new InstanceScope().getNode(IPerformancesConstants.PLUGIN_ID);
125
126	// Init status
127	WRITE_STATUS = this.preferences.getInt(IPerformancesConstants.PRE_WRITE_STATUS, IPerformancesConstants.DEFAULT_WRITE_STATUS);
128
129}
130
131/*
132 * (non-Javadoc)
133 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#createPartControl(org.eclipse.swt.widgets.Composite)
134 */
135public void createPartControl(Composite parent) {
136	super.createPartControl(parent);
137
138	// Create the viewer
139	this.viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
140
141	// Set the content provider: first level is components list
142	WorkbenchContentProvider contentProvider = new WorkbenchContentProvider() {
143		public Object[] getElements(Object o) {
144			return ComponentsView.this.getElements();
145		}
146	};
147	this.viewer.setContentProvider(contentProvider);
148
149	// Set the label provider
150	WorkbenchLabelProvider labelProvider = new WorkbenchLabelProvider() {
151
152		protected String decorateText(String input, Object element) {
153			String text = super.decorateText(input, element);
154			if (element instanceof BuildResultsElement) {
155				BuildResultsElement buildElement = (BuildResultsElement) element;
156				if (buildElement.isMilestone()) {
157					text = Util.getMilestoneName(buildElement.getName()) + " - "+text;
158				}
159			}
160			return text;
161		}
162
163		// When all scenarios are displayed, then set fingerprints one in bold.
164		public Font getFont(Object element) {
165			Font font = super.getFont(element);
166			if (element instanceof ScenarioResultsElement) {
167//				Action fingerprints = ComponentsView.this.filterNonFingerprints;
168//				if (fingerprints != null && !fingerprints.isChecked()) {
169				boolean fingerprints = ComponentsView.this.preferences.getBoolean(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS, IPerformancesConstants.DEFAULT_FILTER_ADVANCED_SCENARIOS);
170				if (!fingerprints) {
171					ScenarioResultsElement scenarioElement = (ScenarioResultsElement) element;
172					if (scenarioElement.hasSummary()) {
173						return getBoldFont(font);
174					}
175				}
176			}
177			if (element instanceof BuildResultsElement) {
178				BuildResultsElement buildElement = (BuildResultsElement) element;
179				if (Util.isMilestone(buildElement.getName())) {
180					return getBoldFont(font);
181				}
182			}
183			return font;
184		}
185	};
186	this.viewer.setLabelProvider(labelProvider);
187
188	// Set the children sorter
189	ViewerSorter nameSorter = new ViewerSorter() {
190
191		// Sort children using specific comparison (see the implementation
192		// of the #compareTo(Object) in the ResultsElement hierarchy
193		public int compare(Viewer view, Object e1, Object e2) {
194			// Config and Build results are sorted in reverse order
195			if (e1 instanceof BuildResultsElement) {
196				ResultsElement element = (ResultsElement) e2;
197				return element.compareTo(e1);
198			}
199			if (e1 instanceof ResultsElement) {
200				ResultsElement element = (ResultsElement) e1;
201				return element.compareTo(e2);
202			}
203			return super.compare(view, e1, e2);
204		}
205	};
206	this.viewer.setSorter(nameSorter);
207
208	// Add results view as listener to viewer selection changes
209	Display.getDefault().asyncExec(new Runnable() {
210		public void run() {
211			ISelectionChangedListener listener = getResultsView();
212			if (listener != null) {
213				ComponentsView.this.viewer.addSelectionChangedListener(listener);
214			}
215		}
216	});
217
218	// Finalize viewer initialization
219	PlatformUI.getWorkbench().getHelpSystem().setHelp(this.viewer.getControl(), "org.eclipse.test.performance.ui.components");
220	finalizeViewerCreation();
221}
222
223/*
224 * (non-Javadoc)
225 * @see org.eclipse.ui.part.WorkbenchPart#dispose()
226 */
227public void dispose() {
228	if (this.boldFont != null) {
229		this.boldFont.dispose();
230	}
231//	JFaceResources.getResources().destroyImage(this.onlyFingerprintsImageDescriptor);
232	super.dispose();
233}
234
235/*
236 * (non-Javadoc)
237 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#fillLocalPullDown(org.eclipse.jface.action.IMenuManager)
238 */
239void fillFiltersDropDown(IMenuManager manager) {
240	super.fillFiltersDropDown(manager);
241	manager.add(this.filterOldBuilds);
242	manager.add(this.filterLastBuilds);
243	manager.add(new Separator());
244	manager.add(this.filterAdvancedScenarios);
245}
246
247void fillLocalPullDown(IMenuManager manager) {
248	super.fillLocalPullDown(manager);
249	manager.add(new Separator());
250	manager.add(this.writeStatus);
251}
252
253/*
254 * Filter non fingerprints scenarios action run.
255 */
256void filterAdvancedScenarios(boolean fingerprints, boolean updatePreference) {
257	this.results.setFingerprints(fingerprints);
258	if (fingerprints) {
259		this.viewFilters.add(FILTER_ADVANCED_SCENARIOS);
260	} else {
261		this.viewFilters.remove(FILTER_ADVANCED_SCENARIOS);
262	}
263	this.preferences.putBoolean(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS, fingerprints);
264	updateFilters();
265}
266
267/*
268 * Returns the bold font.
269 */
270Font getBoldFont(Font font) {
271	if (this.boldFont == null) {
272		FontData[] fontData = (font==null ? JFaceResources.getDefaultFont() : font).getFontData();
273		FontData boldFontData = new FontData(fontData[0].getName(), fontData[0].getHeight(), SWT.BOLD);
274		this.boldFont = new Font(this.display, boldFontData);
275	}
276	return this.boldFont;
277}
278
279/*
280 * Get all the components from the model.
281 */
282Object[] getElements() {
283	if (this.results == null) {
284		initResults();
285		if (this.filterAdvancedScenarios != null) {
286			this.results.setFingerprints(this.filterAdvancedScenarios.isChecked());
287		}
288	}
289	return this.results.getElements();
290}
291
292/*
293 * Return the components results view.
294 */
295ComponentResultsView getResultsView() {
296	if (this.componentResultsView == null) {
297		this.componentResultsView = (ComponentResultsView) getWorkbenchView("org.eclipse.test.internal.performance.results.ui.ComponentsResultsView");
298	}
299	return this.componentResultsView;
300}
301
302/*
303 * Return the builds view.
304 */
305PerformancesView getSiblingView() {
306	if (this.buildsView == null) {
307		this.buildsView = (PerformancesView) getWorkbenchView("org.eclipse.test.internal.performance.results.ui.BuildsView");
308	}
309	return this.buildsView;
310}
311
312/*
313 * (non-Javadoc)
314 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#makeActions()
315 */
316void makeActions() {
317
318	super.makeActions();
319
320	// Filter non-fingerprints action
321	this.filterAdvancedScenarios = new Action("Advanced &Scenarios", IAction.AS_CHECK_BOX) {
322		public void run() {
323			filterAdvancedScenarios(isChecked(), true/*update preference*/);
324        }
325	};
326	this.filterAdvancedScenarios.setChecked(true);
327	this.filterAdvancedScenarios.setToolTipText("Filter advanced scenarios (i.e. not fingerprint ones)");
328
329	// Write status
330	this.writeStatus = new Action("Write status") {
331		public void run() {
332
333			// Get write directory
334			String filter = (ComponentsView.this.resultsDir == null) ? null : ComponentsView.this.resultsDir.getPath();
335			final File writeDir = changeDir(filter, "Select a directory to write the status");
336			if (writeDir != null) {
337				writeStatus(writeDir);
338			}
339        }
340	};
341	this.writeStatus.setEnabled(true);
342	this.writeStatus.setToolTipText("Write component status to a file");
343
344	// Set filters default
345	this.filterBaselineBuilds.setChecked(true);
346	this.filterNightlyBuilds.setChecked(false);
347}
348
349/* (non-Javadoc)
350 * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener#preferenceChange(org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent)
351 */
352public void preferenceChange(PreferenceChangeEvent event) {
353	String propertyName = event.getKey();
354	Object newValue = event.getNewValue();
355
356	// Filter non-fingerprints change
357	if (propertyName.equals(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS)) {
358		boolean checked = newValue == null ? IPerformancesConstants.DEFAULT_FILTER_ADVANCED_SCENARIOS : "true".equals(newValue);
359		filterAdvancedScenarios(checked, false/*do not update preference*/);
360		this.filterAdvancedScenarios.setChecked(checked);
361	}
362
363	// Filter non-milestone change
364	if (propertyName.equals(IPerformancesConstants.PRE_FILTER_OLD_BUILDS)) {
365		boolean checked = newValue == null ? IPerformancesConstants.DEFAULT_FILTER_OLD_BUILDS : "true".equals(newValue);
366		filterOldBuilds(checked, false/*do not update preference*/);
367		this.filterOldBuilds.setChecked(checked);
368	}
369
370	// Write status
371	if (propertyName.equals(IPerformancesConstants.PRE_WRITE_STATUS)) {
372		WRITE_STATUS = newValue == null ? IPerformancesConstants.DEFAULT_WRITE_STATUS : Integer.parseInt((String)newValue);
373	}
374
375	super.preferenceChange(event);
376}
377
378void restoreState() {
379	super.restoreState();
380
381	// Filter baselines action default
382	if (this.viewState == null) {
383		this.filterBaselineBuilds.setChecked(true);
384		this.viewFilters.add(FILTER_BASELINE_BUILDS);
385	} else {
386		String dir = this.viewState.getString(IPerformancesConstants.PRE_WRITE_RESULTS_DIR);
387		if (dir != null) {
388			this.resultsDir = new File(dir);
389		}
390	}
391
392	// Filter non fingerprints action state
393	boolean checked = this.preferences.getBoolean(IPerformancesConstants.PRE_FILTER_ADVANCED_SCENARIOS, IPerformancesConstants.DEFAULT_FILTER_ADVANCED_SCENARIOS);
394	this.filterAdvancedScenarios.setChecked(checked);
395	if (checked) {
396		this.viewFilters.add(FILTER_ADVANCED_SCENARIOS);
397	}
398}
399
400public void saveState(IMemento memento) {
401	if (this.resultsDir != null) {
402		memento.putString(IPerformancesConstants.PRE_WRITE_RESULTS_DIR, this.resultsDir.getPath());
403	}
404	super.saveState(memento);
405}
406
407/**
408 * Select a results element in the tree.
409 */
410public void select(ComponentResultsElement componentResults, String configName, String scenarioName, String buildName) {
411
412	// Collapse previous expanded components except the requested one
413	// TODO (frederic) also collapse expanded components children elements
414	this.expandedComponents.remove(componentResults);
415	Iterator iterator = this.expandedComponents.iterator();
416	while (iterator.hasNext()) {
417		this.viewer.collapseToLevel(iterator.next(), AbstractTreeViewer.ALL_LEVELS);
418	}
419	this.expandedComponents.clear();
420
421	// Set the tree selection
422	ScenarioResultsElement scenarioResultsElement = (ScenarioResultsElement) componentResults.getResultsElement(scenarioName);
423	if (scenarioResultsElement != null) {
424		ConfigResultsElement configResultsElement = (ConfigResultsElement) scenarioResultsElement.getResultsElement(configName);
425		if (configResultsElement != null) {
426			BuildResultsElement buildResultsElement = (BuildResultsElement) configResultsElement.getResultsElement(buildName);
427			if (buildResultsElement != null) {
428				this.viewer.setSelection(new StructuredSelection(buildResultsElement), true);
429				this.setFocus();
430			}
431		}
432	}
433}
434
435/*
436 * (non-Javadoc)
437 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
438 */
439public void selectionChanged(SelectionChangedEvent event) {
440	super.selectionChanged(event);
441	ResultsElement eventResultsElement = (ResultsElement) ((StructuredSelection)event.getSelection()).getFirstElement();
442	if (eventResultsElement != null) {
443		ResultsElement eventComponentElement = eventResultsElement;
444		if (!(eventComponentElement instanceof ComponentResultsElement)) {
445			while (!(eventComponentElement instanceof ComponentResultsElement)) {
446				eventComponentElement = (ResultsElement) eventComponentElement.getParent(null);
447			}
448			this.expandedComponents.add(eventComponentElement);
449		}
450	}
451}
452
453protected void writeStatus(File writeDir) {
454		this.resultsDir = writeDir;
455		if (this.filterAdvancedScenarios.isChecked()) {
456			writeDir = new File(writeDir, "fingerprints");
457		} else {
458			writeDir = new File(writeDir, "all");
459		}
460		writeDir.mkdir();
461		if ((WRITE_STATUS & IPerformancesConstants.STATUS_VALUES) != 0) {
462			writeDir = new File(writeDir, "values");
463		}
464		int buildsNumber = WRITE_STATUS & IPerformancesConstants.STATUS_BUILDS_NUMBER_MASK;
465		if (buildsNumber > 1) {
466			writeDir = new File(writeDir, Integer.toString(buildsNumber));
467		}
468		writeDir.mkdirs();
469		String prefix = this.results.getName();
470		File resultsFile = new File(writeDir, prefix+".log");
471		File exclusionDir = new File(writeDir, "excluded");
472		exclusionDir.mkdir();
473		File exclusionFile = new File(exclusionDir, prefix+".log");
474		if (resultsFile.exists()) {
475			int i=0;
476			File saveDir = new File(writeDir, "save");
477			saveDir.mkdir();
478			while (true) {
479				String newFileName = prefix+"_";
480				if (i<10) newFileName += "0";
481				newFileName += i;
482				File renamedFile = new File(saveDir, newFileName+".log");
483				if (resultsFile.renameTo(renamedFile)) {
484					File renamedExclusionFile = new File(exclusionDir, newFileName+".log");
485					exclusionFile.renameTo(renamedExclusionFile);
486					break;
487				}
488				i++;
489			}
490		}
491
492		// Write status
493		StringBuffer excluded = this.results.writeStatus(resultsFile, WRITE_STATUS);
494		if (excluded == null) {
495			MessageDialog.openWarning(this.shell, getTitleToolTip(), "The component is not read, hence no results can be written!");
496		}
497
498		// Write exclusion file
499		try {
500			DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(exclusionFile)));
501			try {
502				stream.write(excluded.toString().getBytes());
503			}
504			finally {
505				stream.close();
506			}
507		} catch (FileNotFoundException e) {
508			System.err.println("Can't create exclusion file"+exclusionFile); //$NON-NLS-1$
509		} catch (IOException e) {
510			e.printStackTrace();
511		}
512}
513}