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.io.PrintStream;
23import java.util.*;
24
25import org.eclipse.core.runtime.IProgressMonitor;
26import org.eclipse.core.runtime.OperationCanceledException;
27import org.eclipse.core.runtime.SubMonitor;
28import org.eclipse.test.internal.performance.results.utils.Util;
29
30
31/**
32 * Root class to handle performance results.
33 *
34 * Usually performance results are built for a current build vs. a baseline build.
35 *
36 * This class allow to read all data from releng performance database for given
37 * configurations and scenario pattern.
38 *
39 * Then it provides easy and speedy access to all stored results.
40 */
41public class PerformanceResults extends AbstractResults {
42
43	String[] allBuildNames = null;
44	Map allScenarios;
45	String lastBuildName; // Name of the last used build
46	String baselineName; // Name of the baseline build used for comparison
47	String baselinePrefix;
48	private String scenarioPattern = "%"; //$NON-NLS-1$
49	private String[] components;
50	String[] configNames, sortedConfigNames;
51	String[] configDescriptions, sortedConfigDescriptions;
52	private String configPattern;
53
54	boolean dbRequired;
55	boolean needToUpdateLocalFile;
56
57	/*
58	 * Local class helping to guess remaining time while reading results from DB
59	 */
60	class RemainingTimeGuess {
61		int all, count;
62		long start;
63		double remaining;
64		RemainingTimeGuess(int all, long start) {
65			this.all = all;
66			this.start = start;
67		}
68		String display() {
69			StringBuffer buffer = new StringBuffer(" [elapsed: "); //$NON-NLS-1$
70			long elapsed = getElapsed();
71			buffer.append(Util.timeChrono(elapsed));
72			if (this.count > 0) {
73				buffer.append(" | left: "); //$NON-NLS-1$
74				long remainingTime = getRemainingTime(elapsed);
75				buffer.append(Util.timeChrono(remainingTime));
76				buffer.append(" | end: "); //$NON-NLS-1$
77				buffer.append(Util.timeEnd(remainingTime));
78			}
79			buffer.append(']');
80			return buffer.toString();
81		}
82		private long getRemainingTime(long elapsed) {
83			return (long) ((((double)elapsed) / this.count) * (this.all - this.count));
84	    }
85		private long getElapsed() {
86			return System.currentTimeMillis() - this.start;
87	    }
88	}
89
90
91	// Failure threshold
92	public static final int DEFAULT_FAILURE_THRESHOLD = 10;
93	int failure_threshold = DEFAULT_FAILURE_THRESHOLD;
94
95public PerformanceResults(PrintStream stream) {
96	super(null, null);
97	this.printStream = stream;
98	this.dbRequired = false;
99	setDefaults();
100}
101
102public PerformanceResults(String name, String baseline, String baselinePrefix, PrintStream stream) {
103	super(null, name);
104	this.baselineName = baseline;
105	this.baselinePrefix = baselinePrefix;
106	this.printStream = stream;
107	this.dbRequired = true;
108	setDefaults();
109}
110
111/**
112 * Returns the list of all builds currently read.
113 *
114 * @return The names list of all currently known builds
115 */
116public String[] getAllBuildNames() {
117	if (this.allBuildNames == null) {
118		setAllBuildNames();
119	}
120	return this.allBuildNames;
121}
122
123/**
124 * Returns the name of the baseline used for extracted results
125 *
126 * @return The build name of the baseline of <code>null</code>
127 * 	if no specific baseline is used for the extracted results.
128 */
129public String getBaselineName() {
130	return this.baselineName;
131}
132
133/*
134 * Get the baseline prefix (computed from #baselineName).
135 */
136String getBaselinePrefix() {
137	return this.baselinePrefix;
138}
139
140/*
141 * Get the build date (see #getBuildDate(String, String)).
142 */
143public String getBuildDate() {
144	String buildName = getName();
145	if (buildName == null) return ""; //$NON-NLS-1$
146	return Util.getBuildDate(getName(), getBaselinePrefix());
147}
148
149/**
150 * Return the list of components concerned by performance results.
151 *
152 * @return The list of the components
153 */
154public String[] getComponents() {
155	return this.components;
156}
157
158/**
159 * Get the scenarios of a given component.
160 *
161 * @param componentName The component name. Should not be <code>null</code>
162 * @return A list of {@link ScenarioResults scenario results}
163 */
164public List getComponentScenarios(String componentName) {
165	ComponentResults componentResults = (ComponentResults) getResults(componentName);
166	if (componentResults == null) return null;
167	return Collections.unmodifiableList(componentResults.children);
168}
169
170/**
171 * Get the scenarios which have a summary for a given component.
172 *
173 * @param componentName The component name
174 * @param config Configuration name
175 * @return A list of {@link ScenarioResults scenario results} which have a summary
176 */
177public List getComponentSummaryScenarios(String componentName, String config) {
178	if (componentName == null) {
179		int size = size();
180		List scenarios = new ArrayList();
181		for (int i=0; i< size; i++) {
182			ComponentResults componentResults = (ComponentResults) this.children.get(i);
183			scenarios.addAll(componentResults.getSummaryScenarios(true, config));
184		}
185		return scenarios;
186	}
187	ComponentResults componentResults = (ComponentResults) getResults(componentName);
188	return componentResults.getSummaryScenarios(false, config);
189}
190
191/**
192 * Return the configuration boxes considered for this performance results
193 * sorted or not depending on the given flag.
194 *
195 * @param sort Indicates whether the list must be sorted or not.
196 * 	The order is defined by the configuration names, not by the box names
197 * @return The list of configuration boxes sorted by configuration names
198 */
199public String[] getConfigBoxes(boolean sort) {
200	return sort ? this.sortedConfigDescriptions : this.configDescriptions;
201}
202
203/**
204 * Return the configuration names considered for this performance results
205 * sorted or not depending on the given flag.
206 *
207 * @param sort Indicates whether the list must be sorted or not
208 * @return The list of configuration names
209 */
210public String[] getConfigNames(boolean sort) {
211	return sort ?this.sortedConfigNames : this.configNames;
212}
213
214/*
215 * Compute a SQL pattern from all stored configuration names.
216 * For example 'eclipseperflnx1', 'eclipseperflnx2' and 'eclipseperflnx3'
217 * will return 'eclipseperflnx_'.
218 */
219String getConfigurationsPattern() {
220	if (this.configPattern == null) {
221		int length = this.sortedConfigNames == null ? 0 : this.sortedConfigNames.length;
222		if (length == 0) return null;
223		this.configPattern = this.sortedConfigNames[0];
224		int refLength = this.configPattern.length();
225		for (int i=1; i<length; i++) {
226			String config = this.sortedConfigNames[i];
227			StringBuffer newConfig = null;
228			if (refLength != config.length()) return null; // strings have not the same length => cannot find a pattern
229			for (int j=0; j<refLength; j++) {
230				char c = this.configPattern.charAt(j);
231				if (config.charAt(j) != c) {
232					if (newConfig == null) {
233						newConfig = new StringBuffer(refLength);
234						if (j == 0) return null; // first char is already different => cannot find a pattern
235						newConfig.append(this.configPattern.substring(0, j));
236					}
237					newConfig.append('_');
238				} else if (newConfig != null) {
239					newConfig.append(c);
240				}
241			}
242			if (newConfig != null) {
243				this.configPattern = newConfig.toString();
244			}
245		}
246	}
247	return this.configPattern;
248}
249
250/**
251 * Return the name of the last build name except baselines.
252 *
253 * @return the name of the last build
254 */
255public String getLastBuildName() {
256	return getLastBuildName(1/*all except baselines*/);
257}
258/**
259 * Return the name of the last build name
260 *
261 * @param kind Decide what kind of build is taken into account
262 * 	0: all kind of build
263 * 	1: all except baseline builds
264 * 	2: all except baseline and nightly builds
265 * 	3: only integration builds
266 * @return the name of the last build of the selected kind
267 */
268public String getLastBuildName(int kind) {
269	if (this.name == null) {
270		getAllBuildNames(); // init build names if necessary
271		int idx = this.allBuildNames.length-1;
272		this.name = this.allBuildNames[idx];
273		if (kind > 0) {
274			loop: while (idx-- >= 0) {
275				switch (this.name.charAt(0)) {
276					case 'N':
277						if (kind < 2) break loop;
278						break;
279					case 'M':
280						if (kind < 3) break loop;
281						break;
282					case 'I':
283						if (kind < 4) break loop;
284						break;
285				}
286				this.name = this.allBuildNames[idx];
287			}
288		}
289	}
290	return this.name;
291}
292
293public String getName() {
294	if (this.name == null) {
295		setAllBuildNames();
296	}
297	return this.name;
298}
299
300/*
301 * (non-Javadoc)
302 * @see org.eclipse.test.internal.performance.results.AbstractResults#getPerformance()
303 */
304PerformanceResults getPerformance() {
305	return this;
306}
307
308/**
309 * Get the results of a given scenario.
310 *
311 * @param scenarioName The scenario name
312 * @return The {@link ScenarioResults scenario results}
313 */
314public ScenarioResults getScenarioResults(String scenarioName) {
315	ComponentResults componentResults = (ComponentResults) getResults(DB_Results.getComponentNameFromScenario(scenarioName));
316	return componentResults == null ? null : (ScenarioResults) componentResults.getResults(scenarioName);
317}
318
319/*
320 * Init configurations from performance results database.
321 */
322private void initConfigs() {
323	// create config names
324	this.configNames = DB_Results.getConfigs();
325	this.configDescriptions = DB_Results.getConfigDescriptions();
326	int length = this.configNames.length;
327	this.sortedConfigNames = new String[length];
328	for (int i = 0; i < length; i++) {
329	    this.sortedConfigNames[i] = this.configNames[i];
330	}
331
332	// Sort the config names
333	Arrays.sort(this.sortedConfigNames);
334	this.sortedConfigDescriptions = new String[length];
335	for (int i=0; i<length; i++) {
336		for (int j=0; j<length; j++) {
337			if (this.sortedConfigNames[i] == this.configNames[j]) { // == is intentional!
338				this.sortedConfigDescriptions[i] = this.configDescriptions[j];
339				break;
340			}
341		}
342	}
343}
344
345/*
346 * Read or update data for a build from a directory supposed to have local files.
347 */
348private String[] read(boolean local, String buildName, String[][] configs, boolean force, File dataDir, String taskName, SubMonitor subMonitor) {
349	if (local && dataDir == null) {
350		throw new IllegalArgumentException("Must specify a directory to read local files!"); //$NON-NLS-1$
351	}
352	subMonitor.setWorkRemaining(100);
353
354	// Update info
355	long start = System.currentTimeMillis();
356	int allScenariosSize;
357	if (DB_Results.DB_CONNECTION) {
358		try {
359			// Read all scenarios
360			allScenariosSize = readScenarios(buildName, subMonitor.newChild(10)) ;
361			if (allScenariosSize < 0) {
362				return null;
363			}
364
365			// Read all builds
366			DB_Results.queryAllVariations(getConfigurationsPattern());
367
368			// Refresh configs
369			if (configs == null) {
370				initConfigs();
371			} else {
372				setConfigInfo(configs);
373			}
374		} catch (OperationCanceledException e) {
375			return null;
376		}
377	} else {
378		if (this.allScenarios == null) return null;
379		allScenariosSize = this.allScenarios.size();
380		if (configs != null) {
381			setConfigInfo(configs);
382		}
383	}
384
385	// Create corresponding children
386	int componentsLength = this.components.length;
387	subMonitor.setWorkRemaining(componentsLength);
388	RemainingTimeGuess timeGuess = null;
389	for (int i=0; i<componentsLength; i++) {
390		String componentName = this.components[i];
391		List scenarios = this.allScenarios == null ? null : (List) this.allScenarios.get(componentName);
392
393		// Manage monitor
394		int percentage = (int) ((((double)(i+1)) / (componentsLength+1)) * 100);
395		StringBuffer tnBuffer= taskName==null ? new StringBuffer() : new StringBuffer(taskName);
396		tnBuffer.append(" ("); //$NON-NLS-1$
397		if (buildName != null) {
398			tnBuffer.append(buildName).append(": "); //$NON-NLS-1$
399		}
400		tnBuffer.append(percentage).append("%)"); //$NON-NLS-1$
401		subMonitor.setTaskName(tnBuffer.toString());
402		StringBuffer subTaskBuffer = new StringBuffer("Component "); //$NON-NLS-1$
403		subTaskBuffer.append(componentName);
404		subTaskBuffer.append("..."); //$NON-NLS-1$
405		subMonitor.subTask(subTaskBuffer.toString());
406
407		// Get component results
408		if (scenarios == null && !local) continue;
409		ComponentResults componentResults;
410		if (local || (buildName == null && force)) {
411			componentResults = new ComponentResults(this, componentName);
412			addChild(componentResults, true);
413		} else {
414			componentResults = (ComponentResults) getResults(componentName);
415			if (componentResults == null) {
416				componentResults = new ComponentResults(this, componentName);
417				addChild(componentResults, true);
418			}
419		}
420
421		// Read the component results
422		if (local) {
423			try {
424				componentResults.readLocalFile(dataDir, scenarios);
425			}
426			catch (FileNotFoundException ex) {
427				return null;
428			}
429			subMonitor.worked(1);
430		} else {
431			if (timeGuess == null) {
432				timeGuess = new RemainingTimeGuess(1+componentsLength+allScenariosSize, start);
433			}
434			componentResults.updateBuild(buildName, scenarios, force, dataDir, subMonitor.newChild(1), timeGuess);
435		}
436		if (subMonitor.isCanceled()) return null;
437	}
438
439	// Update names
440	setAllBuildNames();
441	writeData(dataDir);
442
443	// Print time
444	printGlobalTime(start);
445
446	return this.allBuildNames;
447}
448
449/**
450 * Read all data from performance database for the given configurations
451 * and scenario pattern.
452 *
453 * This method is typically called when generated performance results
454 * from a non-UI application.
455 *
456 * @param buildName The name of the build
457 * @param configs All configurations to extract results. If <code>null</code>,
458 * 	then all known configurations ({@link DB_Results#getConfigs()})  are read.
459 * @param pattern The pattern of the concerned scenarios
460 * @param dataDir The directory where data will be read/stored locally.
461 * 	If <code>null</code>, then database will be read instead and no storage
462 * 	will be performed
463 * @param threshold The failure percentage threshold over which a build result
464 * 	value compared to the baseline is considered as failing.
465 * @param monitor The progress monitor
466 *
467 * @return All known build names
468 */
469public String[] readAll(String buildName, String[][] configs, String pattern, File dataDir, int threshold, IProgressMonitor monitor) {
470
471	// Init
472	this.scenarioPattern = pattern == null ? "%" : pattern; //$NON-NLS-1$
473	this.failure_threshold = threshold;
474	SubMonitor subMonitor = SubMonitor.convert(monitor, 1000);
475
476	// Set default names
477	setDefaults();
478
479	// Read local data files first
480	reset(dataDir);
481	String[] names = read(true, null, configs, true, dataDir, null, subMonitor.newChild(100));
482	if (names==null) {
483		// if one local files is missing then force a full DB read!
484		// TODO moderate this to force the DB read only for the missing file...
485		return read(false, null, configs, true, dataDir, null, subMonitor.newChild(900));
486	}
487
488	// Search build name in read data
489	boolean buildMissing = true;
490	if (buildName != null) {
491		this.name = buildName;
492		buildMissing = Arrays.binarySearch(names, buildName, Util.BUILD_DATE_COMPARATOR) < 0;
493	}
494
495	// Look for missing builds
496	if (buildMissing) {
497		String[] builds = DB_Results.getBuilds();
498		Arrays.sort(builds, Util.BUILD_DATE_COMPARATOR);
499		for (int i=builds.length-1; i>=0; i--) {
500			if (Arrays.binarySearch(names, builds[i], Util.BUILD_DATE_COMPARATOR) >= 0) {
501				break;
502			}
503			read(false, builds[i], configs, true, dataDir, null, subMonitor.newChild(900));
504		}
505	}
506	return this.allBuildNames;
507}
508
509/**
510 * Read all data from performance database for the given configurations
511 * and scenario pattern.
512 *
513 * Note that calling this method flush all previous read data.
514 *
515 * @param dataDir The directory where local files are located
516 * @param monitor  The progress monitor
517 */
518public void readLocal(File dataDir, IProgressMonitor monitor) {
519
520	// Print title
521	String taskName = "Read local performance results"; //$NON-NLS-1$
522	println(taskName);
523
524	// Create monitor
525	SubMonitor subMonitor = SubMonitor.convert(monitor, 1000);
526	subMonitor.setTaskName(taskName);
527
528	// Read
529	reset(dataDir);
530	read(true, null, null, true, dataDir, taskName, subMonitor);
531}
532
533void readLocalFile(File dir) {
534	if (!dir.exists()) return;
535	File dataFile = new File(dir, "performances.dat");	//$NON-NLS-1$
536	if (!dataFile.exists()) return;
537	DataInputStream stream = null;
538	try {
539		// Read local file info
540		stream = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile)));
541
542		// Read build info
543		String str = stream.readUTF();
544		this.needToUpdateLocalFile = this.name == null || Util.getBuildDate(this.name).compareTo(Util.getBuildDate(str)) > 0;
545		if (this.name != null && Util.getBuildDate(this.name).compareTo(Util.getBuildDate(str)) >= 0) {
546			return;
547		}
548		println(" - read performance results local files info: "); //$NON-NLS-1$
549		println("		+ name : "+str);
550		this.name = str == ""  ? null : str;
551		str = stream.readUTF();
552		println("		+ baseline : "+str);
553		if (this.baselineName == null) {
554			this.baselineName = str == "" ? null : str;
555		}
556		str = stream.readUTF();
557		println("		+ baseline prefix: "+str);
558		this.baselinePrefix = str == "" ? null : str;
559
560		// Write configs info
561		int length = stream.readInt();
562		println("		+ "+length+" configs");
563		this.configNames = new String[length];
564		this.sortedConfigNames = new String[length];
565		this.configDescriptions = new String[length];
566		this.sortedConfigDescriptions = new String[length];
567		for (int i = 0; i < length; i++) {
568			this.configNames[i] = stream.readUTF();
569			this.sortedConfigNames[i] = this.configNames[i];
570			this.configDescriptions[i] = stream.readUTF();
571			this.sortedConfigDescriptions[i] = this.configDescriptions[i];
572		}
573		DB_Results.setConfigs(this.configNames);
574		DB_Results.setConfigDescriptions(this.configDescriptions);
575
576		// Write builds info
577		length = stream.readInt();
578		println("		+ "+length+" builds");
579		this.allBuildNames = new String[length];
580		for (int i = 0; i < length; i++) {
581			this.allBuildNames[i] = stream.readUTF();
582		}
583
584		// Write scenarios info
585		length = stream.readInt();
586		println("		+ "+length+" components");
587		this.components = new String[length];
588		this.allScenarios = new HashMap();
589		for (int i = 0; i < length; i++) {
590			this.components[i] = stream.readUTF();
591			int size = stream.readInt();
592			List scenarios = new ArrayList(size);
593			for (int j=0; j<size; j++) {
594				scenarios.add(new ScenarioResults(stream.readInt(), stream.readUTF(), stream.readUTF()));
595			}
596			this.allScenarios.put(this.components[i], scenarios);
597		}
598		println("	=> read from file "+dataFile); //$NON-NLS-1$
599	} catch (IOException ioe) {
600		println("	!!! "+dataFile+" should be deleted as it contained invalid data !!!"); //$NON-NLS-1$ //$NON-NLS-2$
601	} finally {
602		try {
603	        stream.close();
604        } catch (IOException e) {
605	        // nothing else to do!
606        }
607	}
608}
609
610private int readScenarios(String buildName, SubMonitor subMonitor) throws OperationCanceledException {
611	subMonitor.setWorkRemaining(10);
612	long start = System.currentTimeMillis();
613	String titleSuffix;
614	if (buildName == null) {
615		titleSuffix = "all database scenarios..."; //$NON-NLS-1$
616	} else {
617		titleSuffix = "all database scenarios for "+buildName+" build..."; //$NON-NLS-1$ //$NON-NLS-2$
618	}
619	print("	+ get "+titleSuffix); //$NON-NLS-1$
620	subMonitor.subTask("Get "+titleSuffix); //$NON-NLS-1$
621	this.allScenarios = DB_Results.queryAllScenarios(this.scenarioPattern, buildName);
622	if (this.allScenarios == null) return -1;
623	int allScenariosSize = 0;
624	List componentsSet = new ArrayList(this.allScenarios.keySet());
625	Collections.sort(componentsSet);
626	int componentsSize = componentsSet.size();
627	componentsSet.toArray(this.components = new String[componentsSize]);
628	for (int i=0; i<componentsSize; i++) {
629		String componentName = this.components[i];
630		List scenarios = (List) this.allScenarios.get(componentName);
631		allScenariosSize += scenarios.size();
632	}
633	println(" -> "+allScenariosSize+" found in "+(System.currentTimeMillis()-start)+"ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
634	subMonitor.worked(10);
635	if (subMonitor.isCanceled()) throw new OperationCanceledException();
636	return allScenariosSize;
637}
638
639void reset(File dataDir) {
640	this.allBuildNames = null;
641	this.children = new ArrayList();
642//	this.name = null;
643	this.components = null;
644	this.allScenarios = null;
645	readLocalFile(dataDir);
646}
647
648private void setAllBuildNames() {
649	SortedSet builds = new TreeSet(Util.BUILD_DATE_COMPARATOR);
650	int size = size();
651	if (size == 0) return;
652	for (int i=0; i<size; i++) {
653		ComponentResults componentResults = (ComponentResults) this.children.get(i);
654		Set names = componentResults.getAllBuildNames();
655		builds.addAll(names);
656	}
657	int buildsSize = builds.size();
658	this.allBuildNames = new String[buildsSize];
659	if (buildsSize > 0) {
660		int n = 0;
661		Iterator buildNames = builds.iterator();
662		while (buildNames.hasNext()) {
663			String buildName = (String) buildNames.next();
664			if (this.lastBuildName == null || Util.getBuildDate(buildName).compareTo(Util.getBuildDate(this.lastBuildName)) <= 0) {
665				this.allBuildNames[n++] = buildName;
666			}
667		}
668		if (n < buildsSize) {
669			System.arraycopy(this.allBuildNames, 0, this.allBuildNames = new String[n], 0, n);
670		}
671		int idx = n-1;
672		String lastBuild = this.allBuildNames[idx--];
673		while (lastBuild.startsWith(DB_Results.getDbBaselinePrefix())) {
674			lastBuild = this.allBuildNames[idx--];
675		}
676		this.needToUpdateLocalFile = this.name == null || Util.getBuildDate(lastBuild).compareTo(Util.getBuildDate(this.name)) > 0;
677		this.name = lastBuild;
678		if (this.baselineName != null) {
679			String lastBuildDate = Util.getBuildDate(lastBuild);
680			if (Util.getBuildDate(this.baselineName).compareTo(lastBuildDate) > 0) {
681				this.baselineName = DB_Results.getLastBaselineBuild(lastBuildDate);
682			}
683		}
684	}
685}
686
687private void setConfigInfo(String[][] configs) {
688	if (configs == null) return;
689
690	// Store config information
691	int length = configs.length;
692	this.configNames = new String[length];
693	this.sortedConfigNames = new String[length];
694	this.configDescriptions = new String[length];
695	for (int i=0; i<length; i++) {
696		this.configNames[i] = this.sortedConfigNames[i] = configs[i][0];
697		this.configDescriptions[i] = configs[i][1];
698	}
699
700	// Sort the config names
701	Arrays.sort(this.sortedConfigNames);
702	length = this.sortedConfigNames.length;
703	this.sortedConfigDescriptions = new String[length];
704	for (int i=0; i<length; i++) {
705		for (int j=0; j<length; j++) {
706			if (this.sortedConfigNames[i] == this.configNames[j]) { // == is intentional!
707				this.sortedConfigDescriptions[i] = this.configDescriptions[j];
708				break;
709			}
710		}
711	}
712}
713
714
715/**
716 * Set the name of the baseline used for extracted results
717 *
718 * @param buildName The name of the baseline build
719 */
720public void setBaselineName(String buildName) {
721	this.baselineName = buildName;
722}
723
724private void setDefaults() {
725
726	// Set builds if none
727	if (size() == 0 && DB_Results.DB_CONNECTION) {
728		this.allBuildNames = DB_Results.getBuilds();
729		this.components = DB_Results.getComponents();
730		initConfigs();
731	}
732
733	// Set name if null
734	if (this.name == null) {
735		setAllBuildNames();
736		if (this.name == null) { // does not know any build
737			this.name = DB_Results.getLastCurrentBuild();
738			if (this.dbRequired) {
739				if (this.name == null) {
740					throw new RuntimeException("Cannot find any current build!"); //$NON-NLS-1$
741				}
742				this.allBuildNames = DB_Results.getBuilds();
743				this.components = DB_Results.getComponents();
744				initConfigs();
745			}
746			if (this.printStream != null) {
747				this.printStream.println("	+ no build specified => use last one: "+this.name); //$NON-NLS-1$
748			}
749		}
750	}
751
752	// Init baseline name if not set
753	if (this.baselineName == null && getName() != null) {
754		String buildDate = Util.getBuildDate(getName());
755		this.baselineName = DB_Results.getLastBaselineBuild(buildDate);
756		if (this.baselineName == null && this.dbRequired) {
757			throw new RuntimeException("Cannot find any baseline to refer!"); //$NON-NLS-1$
758		}
759		if (this.printStream != null) {
760			this.printStream.println("	+ no baseline specified => use last one: "+this.baselineName); //$NON-NLS-1$
761		}
762	}
763
764	// Init baseline prefix if not set
765	if (this.baselinePrefix == null && this.baselineName != null) {
766		// Assume that baseline name format is *always* x.y_yyyyMMddhhmm_yyyyMMddhhmm
767		int index = this.baselineName.lastIndexOf('_');
768		if (index > 0) {
769			this.baselinePrefix = this.baselineName.substring(0, index);
770		} else {
771			this.baselinePrefix = DB_Results.getDbBaselinePrefix();
772		}
773	}
774
775	// Set scenario pattern default
776	if (this.scenarioPattern == null) {
777		this.scenarioPattern = "%"; //$NON-NLS-1$
778	}
779
780	// Flush print stream
781	if (this.printStream != null) {
782		this.printStream.println();
783		this.printStream.flush();
784	}
785}
786
787public void setLastBuildName(String lastBuildName) {
788	this.lastBuildName = lastBuildName;
789//	if (lastBuildName == null) {
790//		int idx = this.allBuildNames.length-1;
791//		String lastBuild = this.allBuildNames[idx--];
792//		while (this.name.startsWith(DB_Results.getDbBaselinePrefix())) {
793//			lastBuild = this.allBuildNames[idx--];
794//		}
795//		this.name = lastBuild;
796//	} else {
797//		this.name = lastBuildName;
798//	}
799}
800
801/**
802 * Update a given build information with database contents.
803 *
804 * @param builds The builds to read new data
805 * @param force Force the update from the database, even if the build is
806 * 	already known.
807 * @param dataDir The directory where data should be stored locally if necessary.
808 * 	If <code>null</code>, then information changes won't be persisted.
809 * @param monitor The progress monitor
810 * @return All known builds
811 */
812public String[] updateBuilds(String[] builds, boolean force, File dataDir, IProgressMonitor monitor) {
813
814	// Print title
815	StringBuffer buffer = new StringBuffer("Update data for "); //$NON-NLS-1$
816	int length = builds == null ? 0 : builds.length;
817	switch (length) {
818		case 0:
819			buffer.append("all builds"); //$NON-NLS-1$
820			reset(dataDir);
821			break;
822		case 1:
823			buffer.append("one build"); //$NON-NLS-1$
824			break;
825		default:
826			buffer.append("several builds"); //$NON-NLS-1$
827			break;
828	}
829	String taskName = buffer.toString();
830	println(buffer);
831
832	// Create sub-monitor
833	SubMonitor subMonitor = SubMonitor.convert(monitor, 1000*length);
834	subMonitor.setTaskName(taskName);
835
836	// Read
837	for (int i=0; i<length;  i++) {
838		read(false, builds[i], null, force, dataDir, taskName, subMonitor.newChild(1000));
839	}
840
841	// Return new builds list
842	return this.allBuildNames;
843}
844
845/**
846 * Update a given build information with database contents.
847 *
848 * @param buildName The build name to read new data
849 * @param force Force the update from the database, even if the build is
850 * 	already known.
851 * @param dataDir The directory where data should be stored locally if necessary.
852 * 	If <code>null</code>, then information changes won't be persisted.
853 * @param monitor The progress monitor
854 * @return All known builds
855 */
856public String[] updateBuild(String buildName, boolean force, File dataDir, IProgressMonitor monitor) {
857
858	// Print title
859	StringBuffer buffer = new StringBuffer("Update data for "); //$NON-NLS-1$
860	if (buildName == null) {
861		buffer.append("all builds"); //$NON-NLS-1$
862		reset(dataDir);
863	} else {
864		buffer.append("one build"); //$NON-NLS-1$
865	}
866	String taskName = buffer.toString();
867	println(buffer);
868
869	// Create sub-monitor
870	SubMonitor subMonitor = SubMonitor.convert(monitor, 1000);
871	subMonitor.setTaskName(taskName);
872
873	// Read
874	read(false, buildName, null, force, dataDir, taskName, subMonitor);
875
876	// Refresh name
877	if (buildName != null && !buildName.startsWith(DB_Results.getDbBaselinePrefix())) {
878		this.name = buildName;
879	}
880
881	// Return new list all build names
882	return this.allBuildNames;
883}
884
885/*
886 * Write general information.
887 */
888void writeData(File dir) {
889	if (!DB_Results.DB_CONNECTION) {
890		// Only write new local file if there's a database connection
891		// otherwise contents may not be complete...
892		return;
893	}
894	if (dir ==null || (!dir.exists() && !dir.mkdirs())) {
895		System.err.println("can't create directory " + dir); //$NON-NLS-1$
896		return;
897	}
898	File dataFile = new File(dir, "performances.dat"); //$NON-NLS-1$
899	if (dataFile.exists()) {
900		if (!this.needToUpdateLocalFile) {
901			return;
902		}
903		dataFile.delete();
904	}
905	try {
906		DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile)));
907
908		// Write build info
909		stream.writeUTF(this.name == null ? DB_Results.getLastCurrentBuild() : this.name);
910		stream.writeUTF(this.baselineName == null ? DB_Results.getLastBaselineBuild(null) : this.baselineName);
911		stream.writeUTF(this.baselinePrefix == null ? "" : this.baselinePrefix);
912
913		// Write configs info
914		int length = this.sortedConfigNames.length;
915		stream.writeInt(length);
916		for (int i = 0; i < length; i++) {
917			stream.writeUTF(this.sortedConfigNames[i]);
918			stream.writeUTF(this.sortedConfigDescriptions[i]);
919		}
920
921		// Write builds info
922		String[] builds = this.allBuildNames == null ? DB_Results.getBuilds() : this.allBuildNames;
923		length = builds.length;
924		stream.writeInt(length);
925		for (int i = 0; i < length; i++) {
926			stream.writeUTF(builds[i]);
927		}
928
929		// Write scenarios info
930		length = this.components.length;
931		stream.writeInt(length);
932		for (int i = 0; i < length; i++) {
933			stream.writeUTF(this.components[i]);
934			List scenarios = (List) this.allScenarios.get(this.components[i]);
935			int size = scenarios.size();
936			stream.writeInt(size);
937			for (int j=0; j<size; j++) {
938				final ScenarioResults scenarioResults = (ScenarioResults)scenarios.get(j);
939				stream.writeInt(scenarioResults.getId());
940				stream.writeUTF(scenarioResults.getName());
941				stream.writeUTF(scenarioResults.getLabel());
942			}
943		}
944
945		// Close
946		stream.close();
947		println("	=> performance results general data  written in file " + dataFile); //$NON-NLS-1$
948	} catch (FileNotFoundException e) {
949		System.err.println("can't create output file" + dataFile); //$NON-NLS-1$
950	} catch (IOException e) {
951		e.printStackTrace();
952	}
953}
954
955}
956