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.db;
12
13import java.io.BufferedInputStream;
14import java.io.BufferedOutputStream;
15import java.io.DataInputStream;
16import java.io.DataOutputStream;
17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FileNotFoundException;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.Comparator;
25import java.util.HashSet;
26import java.util.List;
27import java.util.Set;
28
29import org.eclipse.core.runtime.SubMonitor;
30import org.eclipse.test.internal.performance.results.utils.Util;
31
32/**
33 * Class to handle performance results of an eclipse component
34 * (for example 'org.eclipse.jdt.core').
35 *
36 * It gives access to results for each scenario run for this component.
37 *
38 * @see ScenarioResults
39 */
40public class ComponentResults extends AbstractResults {
41
42public ComponentResults(AbstractResults parent, String name) {
43	super(parent, name);
44	this.printStream = parent.printStream;
45}
46
47Set getAllBuildNames() {
48	Set buildNames = new HashSet();
49	int size = size();
50	for (int i=0; i<size; i++) {
51		ScenarioResults scenarioResults = (ScenarioResults) this.children.get(i);
52		Set builds = scenarioResults.getAllBuildNames();
53		buildNames.addAll(builds);
54	}
55	return buildNames;
56}
57
58/**
59 * Return all the build names for this component sorted by ascending order.
60 *
61 * @return An array of names
62 */
63public String[] getAllSortedBuildNames() {
64	return getAllSortedBuildNames(false/*ascending order*/);
65}
66
67String[] getAllSortedBuildNames(final boolean reverse) {
68	Set allBuildNames = getAllBuildNames();
69	String[] sortedNames = new String[allBuildNames.size()];
70	allBuildNames.toArray(sortedNames);
71	Arrays.sort(sortedNames, new Comparator() {
72		public int compare(Object o1, Object o2) {
73			String s1 = (String) (reverse ? o2 : o1);
74			String s2 = (String) (reverse ? o1 : o2);
75			return Util.getBuildDate(s1).compareTo(Util.getBuildDate(s2));
76	    }
77	});
78	return sortedNames;
79}
80
81ComponentResults getComponentResults() {
82	return this;
83}
84
85/**
86 * Get all results numbers for a given machine of the current component.
87 *
88 * @param configName The name of the configuration to get numbers
89 * @param fingerprints Set whether only fingerprints scenario should be taken into account
90 * @return A list of lines. Each line represent a build and is a list of either strings or values.
91  * 	Values are an array of double:
92 * 	<ul>
93 * 		<li>{@link #BUILD_VALUE_INDEX}: the build value in milliseconds</li>
94 * 		<li>{@link #BASELINE_VALUE_INDEX}: the baseline value in milliseconds</li>
95 * 		<li>{@link #DELTA_VALUE_INDEX}: the difference between the build value and its more recent baseline</li>
96 * 		<li>{@link #DELTA_ERROR_INDEX}: the error made while computing the difference</li>
97 * 		<li>{@link #BUILD_ERROR_INDEX}: the error made while measuring the build value</li>
98 * 		<li>{@link #BASELINE_ERROR_INDEX}: the error made while measuring the baseline value</li>
99 * 	</ul>
100*/
101public List getConfigNumbers(String configName, boolean fingerprints, List differences) {
102
103	// Initialize lists
104	AbstractResults[] scenarios = getChildren();
105	int length = scenarios.length;
106
107	// Print scenario names line
108	List firstLine = new ArrayList();
109	for (int i=0; i<length; i++) {
110		ScenarioResults scenarioResults = (ScenarioResults) scenarios[i];
111		if (!fingerprints || scenarioResults.hasSummary()) {
112			firstLine.add(scenarioResults.getName());
113		}
114	}
115
116	// Print each build line
117	String[] builds = getAllSortedBuildNames(true/*descending order*/);
118//	int milestoneIndex = 0;
119//	String milestoneDate = Util.getMilestoneDate(milestoneIndex);
120	String currentBuildName = null;
121	int buildsLength= builds.length;
122	firstLine.add(0, new Integer(buildsLength));
123	differences.add(firstLine);
124	for (int i=0; i<buildsLength; i++) {
125		List line = new ArrayList();
126		String buildName = builds[i];
127		line.add(buildName);
128		if (!buildName.startsWith(DB_Results.getDbBaselinePrefix())) {
129			for (int j=0; j<length; j++) {
130				ScenarioResults scenarioResults = (ScenarioResults) scenarios[j];
131				if (!fingerprints || scenarioResults.hasSummary()) {
132					ConfigResults configResults = scenarioResults.getConfigResults(configName);
133					BuildResults buildResults = configResults == null ? null : configResults.getBuildResults(buildName);
134					if (buildResults == null) {
135						// no result for this scenario in this build
136						line.add(NO_BUILD_RESULTS);
137					} else {
138						line.add(configResults.getNumbers(buildResults, configResults.getBaselineBuildResults(buildName)));
139					}
140				}
141			}
142			differences.add(line);
143			if (currentBuildName != null && currentBuildName.charAt(0) != 'N') {
144            }
145			currentBuildName = buildName;
146		}
147//		if (milestoneDate != null) { // update previous builds
148//			int dateComparison = milestoneDate.compareTo(Util.getBuildDate(buildName));
149//			if (dateComparison <= 0) {
150//				if (dateComparison == 0) {
151//                }
152//				if (++milestoneIndex == Util.MILESTONES.length) {
153//					milestoneDate = null;
154//				} else {
155//					milestoneDate = Util.getMilestoneDate(milestoneIndex);
156//				}
157//			}
158//		}
159	}
160
161	// Write differences lines
162	int last = buildsLength-1;
163	String lastBuildName = builds[last];
164	while (last > 0 && lastBuildName.startsWith(DB_Results.getDbBaselinePrefix())) {
165		lastBuildName = builds[--last];
166	}
167//	appendDifferences(lastBuildName, configName, previousMilestoneName, differences, fingerprints);
168//	appendDifferences(lastBuildName, configName, previousBuildName, differences, fingerprints);
169
170	// Return the computed differences
171	return differences;
172}
173
174/*
175double[] getConfigNumbers(BuildResults buildResults, BuildResults baselineResults) {
176	if (baselineResults == null) {
177		return INVALID_RESULTS;
178	}
179	double[] values = new double[NUMBERS_LENGTH];
180	for (int i=0 ;i<NUMBERS_LENGTH; i++) {
181		values[i] = Double.NaN;
182	}
183	double buildValue = buildResults.getValue();
184	values[BUILD_VALUE_INDEX] = buildValue;
185	double baselineValue = baselineResults.getValue();
186	values[BASELINE_VALUE_INDEX] = baselineValue;
187	double delta = (baselineValue - buildValue) / baselineValue;
188	values[DELTA_VALUE_INDEX] = delta;
189	if (Double.isNaN(delta)) {
190		return values;
191	}
192	long baselineCount = baselineResults.getCount();
193	long currentCount = buildResults.getCount();
194	if (baselineCount > 1 && currentCount > 1) {
195		double baselineError = baselineResults.getError();
196		double currentError = buildResults.getError();
197		values[BASELINE_ERROR_INDEX] = baselineError;
198		values[BUILD_ERROR_INDEX] = currentError;
199		values[DELTA_ERROR_INDEX] = Double.isNaN(baselineError)
200				? currentError / baselineValue
201				: Math.sqrt(baselineError*baselineError + currentError*currentError) / baselineValue;
202	}
203	return values;
204}
205*/
206
207private ScenarioResults getScenarioResults(List scenarios, int searchedId) {
208	int size = scenarios.size();
209	for (int i=0; i<size; i++) {
210		ScenarioResults scenarioResults = (ScenarioResults) scenarios.get(i);
211		if (scenarioResults.id == searchedId) {
212			return scenarioResults;
213		}
214	}
215	return null;
216}
217
218/**
219 * Returns a list of scenario results which have a summary
220 *
221 * @param global Indicates whether the summary must be global or not.
222 * @param config Configuration name
223 * @return A list of {@link ScenarioResults scenario results} which have a summary
224 */
225public List getSummaryScenarios(boolean global, String config) {
226	int size= size();
227	List scenarios = new ArrayList(size);
228	for (int i=0; i<size; i++) {
229		ScenarioResults scenarioResults = (ScenarioResults) this.children.get(i);
230		ConfigResults configResults = scenarioResults.getConfigResults(config);
231		if (configResults != null) {
232			BuildResults buildResults = configResults.getCurrentBuildResults();
233			if ((global && buildResults.summaryKind == 1) || (!global && buildResults.summaryKind >= 0)) {
234				scenarios.add(scenarioResults);
235			}
236		}
237	}
238	return scenarios;
239}
240
241private String lastBuildName(int kind) {
242	String[] builds = getAllSortedBuildNames();
243	int idx = builds.length-1;
244	String lastBuildName = builds[idx--];
245	switch (kind) {
246		case 1: // no ref
247			while (lastBuildName.startsWith(DB_Results.getDbBaselinePrefix())) {
248				lastBuildName = builds[idx--];
249			}
250			break;
251		case 2: // only I-build or M-build
252			char ch = lastBuildName.charAt(0);
253			while (ch != 'I' && ch != 'M') {
254				lastBuildName = builds[idx--];
255				ch = lastBuildName.charAt(0);
256			}
257			break;
258		default:
259			break;
260	}
261	return lastBuildName;
262}
263
264/*
265 * Read local file contents and populate the results model with the collected
266 * information.
267 */
268String readLocalFile(File dir, List scenarios) throws FileNotFoundException {
269//	if (!dir.exists()) return null;
270	File dataFile = new File(dir, getName()+".dat");	//$NON-NLS-1$
271	if (!dataFile.exists()) throw new FileNotFoundException();
272	DataInputStream stream = null;
273	try {
274		// Read local file info
275		stream = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
276		print(" - read local files info"); //$NON-NLS-1$
277		String lastBuildName = stream.readUTF(); // first string is the build name
278
279		// Next field is the number of scenarios for the component
280		int size = stream.readInt();
281
282		// Then follows all the scenario information
283		for (int i=0; i<size; i++) {
284			// ... which starts with the scenario id
285			int scenario_id = stream.readInt();
286			ScenarioResults scenarioResults = scenarios == null ? null : getScenarioResults(scenarios, scenario_id);
287			if (scenarioResults == null) {
288				// this can happen if scenario pattern does not cover all those stored in local data file
289				// hence, creates a fake scenario to read the numbers and skip to the next scenario
290				scenarioResults = new ScenarioResults(-1, null, null);
291//				scenarioResults.parent = this;
292//				scenarioResults.readData(stream);
293				// Should no longer occur as we get all scenarios from database now
294//				throw new RuntimeException("Unexpected unfound scenario!"); //$NON-NLS-1$
295			}
296			scenarioResults.parent = this;
297			scenarioResults.printStream = this.printStream;
298			scenarioResults.readData(stream);
299			addChild(scenarioResults, true);
300			if (this.printStream != null) this.printStream.print('.');
301		}
302		println();
303		println("	=> "+size+" scenarios data were read from file "+dataFile); //$NON-NLS-1$ //$NON-NLS-2$
304
305		// Return last build name stored in the local files
306		return lastBuildName;
307	} catch (IOException ioe) {
308		println("	!!! "+dataFile+" should be deleted as it contained invalid data !!!"); //$NON-NLS-1$ //$NON-NLS-2$
309	} finally {
310		try {
311	        stream.close();
312        } catch (IOException e) {
313	        // nothing else to do!
314        }
315	}
316	return null;
317}
318
319/*
320 * Read the database values for a build name and a list of scenarios.
321 * The database is read only if the components does not already knows the
322 * given build (i.e. if it has not been already read) or if the force arguments is set.
323 */
324void updateBuild(String buildName, List scenarios, boolean force, File dataDir, SubMonitor subMonitor, PerformanceResults.RemainingTimeGuess timeGuess) {
325
326	// Read all variations
327	println("Component '"+this.name+"':"); //$NON-NLS-1$ //$NON-NLS-2$
328
329	// manage monitor
330	int size = scenarios.size();
331	subMonitor.setWorkRemaining(size+1);
332	StringBuffer buffer = new StringBuffer("Component "); //$NON-NLS-1$
333	buffer.append(this.name);
334	buffer.append("..."); //$NON-NLS-1$
335	String title = buffer.toString();
336	subMonitor.subTask(title+timeGuess.display());
337	timeGuess.count++;
338	subMonitor.worked(1);
339	if (subMonitor.isCanceled()) return;
340
341	// Read new values for the local result
342	boolean dirty = false;
343	long readTime = System.currentTimeMillis();
344	String log = " - read scenarios from DB:"; //$NON-NLS-1$
345	if (size > 0) {
346		for (int i=0; i<size; i++) {
347
348			// manage monitor
349			subMonitor.subTask(title+timeGuess.display());
350			timeGuess.count++;
351			if (log != null) {
352				println(log);
353				log = null;
354			}
355
356			// read results
357			ScenarioResults nextScenarioResults= (ScenarioResults) scenarios.get(i);
358			ScenarioResults scenarioResults = (ScenarioResults) getResults(nextScenarioResults.id);
359			if (scenarioResults == null) {
360				// Scenario is not known yet, force an update
361				scenarioResults = nextScenarioResults;
362				scenarioResults.parent = this;
363				scenarioResults.printStream = this.printStream;
364				scenarioResults.updateBuild(buildName, true);
365				dirty = true;
366				addChild(scenarioResults, true);
367			} else {
368				if (scenarioResults.updateBuild(buildName, force)) {
369					dirty = true;
370				}
371			}
372			if (dataDir != null && dirty && (System.currentTimeMillis() - readTime) > 300000) { // save every 5mn
373				writeData(buildName, dataDir, true, true);
374				dirty = false;
375				readTime = System.currentTimeMillis();
376			}
377
378			// manage monitor
379			subMonitor.worked(1);
380			if (subMonitor.isCanceled()) return;
381		}
382	}
383
384	// Write local files
385	if (dataDir != null) {
386		writeData(buildName, dataDir, false, dirty);
387	}
388
389	// Print global time
390	printGlobalTime(readTime);
391
392}
393
394/*
395 * Write the component results data to the file '<component name>.dat' in the given directory.
396 */
397void writeData(String buildName, File dir, boolean temp, boolean dirty) {
398//	if (!dir.exists() && !dir.mkdirs()) {
399//		System.err.println("can't create directory "+dir); //$NON-NLS-1$
400//	}
401	File tmpFile = new File(dir, getName()+".tmp"); //$NON-NLS-1$
402	File dataFile = new File(dir, getName()+".dat"); //$NON-NLS-1$
403	if (!dirty) { // only possible on final write
404		if (tmpFile.exists()) {
405			if (dataFile.exists()) dataFile.delete();
406			tmpFile.renameTo(dataFile);
407			println("	=> rename temporary file to "+dataFile); //$NON-NLS-1$
408		}
409		return;
410	}
411	if (tmpFile.exists()) {
412		tmpFile.delete();
413	}
414	File file;
415	if (temp) {
416		file = tmpFile;
417	} else {
418		if (dataFile.exists()) {
419			dataFile.delete();
420		}
421		file = dataFile;
422	}
423	try {
424		DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
425		try {
426			int size = this.children.size();
427			stream.writeUTF(lastBuildName(0));
428			stream.writeInt(size);
429			for (int i=0; i<size; i++) {
430				ScenarioResults scenarioResults = (ScenarioResults) this.children.get(i);
431				scenarioResults.write(stream);
432			}
433		}
434		finally {
435			stream.close();
436			println("	=> extracted data "+(temp?"temporarily ":"")+"written in file "+file); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
437		}
438	} catch (FileNotFoundException e) {
439		System.err.println("can't create output file"+file); //$NON-NLS-1$
440	} catch (IOException e) {
441		e.printStackTrace();
442	}
443}
444
445}
446