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.File;
14import java.io.PrintWriter;
15import java.io.StringWriter;
16import java.math.BigDecimal;
17import java.sql.Connection;
18import java.sql.DriverManager;
19import java.sql.ResultSet;
20import java.sql.SQLException;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.HashMap;
24import java.util.List;
25import java.util.Map;
26import java.util.StringTokenizer;
27
28import org.eclipse.core.runtime.Assert;
29import org.eclipse.test.internal.performance.PerformanceTestPlugin;
30import org.eclipse.test.internal.performance.data.Dim;
31import org.eclipse.test.internal.performance.db.DB;
32import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
33import org.eclipse.test.internal.performance.results.utils.Util;
34import org.eclipse.test.performance.Dimension;
35
36/**
37 * Specific and private implementation of {@link org.eclipse.test.internal.performance.db.DB} class
38 * to get massive results from performance results database.
39 * TODO (frederic) Should be at least a subclass of {@link DB}...
40 */
41public class DB_Results {
42
43
44	private static final String DEFAULT_DB_BASELINE_PREFIX = "R-";
45	private static final Dim[] NO_DIMENSION = new Dim[0];
46	private static final String[] EMPTY_LIST = new String[0];
47	static final boolean DEBUG = false;
48    static final boolean LOG = false;
49
50    // the two supported DB types
51    private static final String DERBY= "derby"; //$NON-NLS-1$
52    private static final String CLOUDSCAPE= "cloudscape"; //$NON-NLS-1$
53
54    private static DB_Results fgDefault;
55
56    private Connection fConnection;
57    private SQL_Results fSQL;
58//    private boolean fIsEmbedded;
59    private String fDBType;	// either "derby" or "cloudscape"
60
61	    // Preferences info
62    public static boolean DB_CONNECTION = false;
63    private static String DB_NAME;
64    private static String DB_LOCATION;
65	private static String DB_BASELINE_PREFIX = DEFAULT_DB_BASELINE_PREFIX;
66	private static String DB_VERSION;
67	private static String DB_VERSION_REF;
68
69	/**
70	 * Get the name of the database.
71	 *
72	 * @return The name as a string.
73	 */
74    public static String getDbName() {
75    	if (DB_NAME == null) initDbContants();
76    	return DB_NAME;
77    }
78
79	/**
80	 * Set the name of the database.
81	 *
82	 * @param dbName The name as a string.
83	 */
84    public static void setDbName(String dbName) {
85    	Assert.isNotNull(dbName);
86    	DB_NAME = dbName;
87    }
88
89	/**
90	 * Get the location of the database.
91	 *
92	 * @return The location as a string.
93	 */
94    public static String getDbLocation() {
95    	if (!DB_CONNECTION) return null;
96    	if (DB_LOCATION == null) initDbContants();
97    	return DB_LOCATION;
98    }
99
100	/**
101	 * Set the location of the database.
102	 *
103	 * @param dbLocation The location as a string.
104	 */
105    public static void setDbLocation(String dbLocation) {
106    	Assert.isNotNull(dbLocation);
107    	DB_LOCATION = dbLocation;
108    }
109
110	/**
111	 * Get the default baseline prefix.
112	 *
113	 * @return The prefix as a string.
114	 */
115    public static String getDbBaselinePrefix() {
116    	return DB_BASELINE_PREFIX;
117    }
118
119	/**
120	 * Set the baseline prefix of the database.
121	 *
122	 * @param baselinePrefix The prefix as a string.
123	 */
124    public static void setDbDefaultBaselinePrefix(String baselinePrefix) {
125    	Assert.isNotNull(baselinePrefix);
126    	Assert.isTrue(baselinePrefix.startsWith(DEFAULT_DB_BASELINE_PREFIX));
127    	DB_BASELINE_PREFIX = baselinePrefix;
128    }
129
130	/**
131	 * Get the baseline reference version of the database.
132	 *
133	 * @return The version as a string.
134	 */
135    public static String getDbBaselineRefVersion() {
136    	if (DB_VERSION_REF == null) initDbContants();
137    	return DB_VERSION_REF;
138    }
139
140	/**
141	 * Get the version of the database.
142	 *
143	 * @return The version as a string.
144	 */
145    public static String getDbVersion() {
146    	if (DB_VERSION == null) initDbContants();
147    	return DB_VERSION;
148    }
149
150	/**
151	 * Set the version of the database.
152	 *
153	 * @param version The version as a string.
154	 */
155    public static void setDbVersion(String version) {
156    	Assert.isNotNull(version);
157    	Assert.isTrue(version.startsWith("v3"));
158    	DB_VERSION = version;
159    }
160
161	/**
162	 * Update the database constants from a new database location.
163	 * @param connected Tells whether the database should be connected or not.
164	 * @param databaseLocation The database location.
165	 * 	May be a path to a local folder or a net address
166	 * 	(see {@link IPerformancesConstants#NETWORK_DATABASE_LOCATION}).
167	 */
168	public static boolean updateDbConstants(boolean connected, int eclipseVersion, String databaseLocation) {
169		if (DB_CONNECTION != connected || DB_LOCATION == null || DB_NAME == null ||
170			((databaseLocation == null && !DB_LOCATION.equals(IPerformancesConstants.NETWORK_DATABASE_LOCATION)) ||
171					!DB_LOCATION.equals(databaseLocation)) ||
172			!DB_NAME.equals(IPerformancesConstants.DATABASE_NAME_PREFIX + eclipseVersion)) {
173			shutdown();
174			DB_CONNECTION = connected;
175			DB_LOCATION = databaseLocation == null ? IPerformancesConstants.NETWORK_DATABASE_LOCATION : databaseLocation;
176			DB_NAME = IPerformancesConstants.DATABASE_NAME_PREFIX + eclipseVersion;
177			DB_VERSION = "v" + eclipseVersion;
178			DB_VERSION_REF = "R-3." + (eclipseVersion % 10 - 1);
179			if (connected) {
180				return getDefault().fSQL != null;
181			}
182		}
183		return true;
184	}
185
186	/**
187	 * Returns a title including DB version and name.
188	 *
189	 * @return A title as a string.
190	 */
191	public static String getDbTitle() {
192    	if (!DB_CONNECTION) return null;
193		String title = "Eclipse " + DB_VERSION + " - ";
194		if (DB_LOCATION.startsWith("net:")) {
195			title += " Network DB";
196		} else {
197			title += " Local DB";
198		}
199		return title;
200	}
201
202	/**
203	 * The list of all the configurations (i.e. machine) stored in the database.
204	 */
205	private static String[] CONFIGS;
206
207	/**
208	 * The list of all the components stored in the database.
209	 */
210	private static String[] COMPONENTS;
211
212	/**
213	 * The list of all the builds stored in the database.
214	 */
215	private static String[] BUILDS;
216
217	/**
218	 * The list of all the dimensions stored in the database.
219	 */
220	private static int[] DIMENSIONS;
221
222	/**
223	 * The default dimension used to display results (typically in fingerprints).
224	 */
225	private static Dim DEFAULT_DIM;
226	private static int DEFAULT_DIM_INDEX;
227
228	/**
229	 * The list of all the dimensions displayed while generating results.
230	 */
231	private static Dim[] RESULTS_DIMENSIONS;
232
233	/**
234	 * The list of all the VMs stored in the database.
235	 */
236	private static String[] VMS;
237
238	/**
239	 * The list of possible test boxes.
240	 * <p>
241	 * Only used if no specific configurations are specified
242	 * (see {@link PerformanceResults#readAll(String, String[][], String, File, int, org.eclipse.core.runtime.IProgressMonitor)}.
243	 * </p>
244	 * Note that this is a copy of the the property "eclipse.perf.config.descriptors"
245	 * defined in org.eclipse.releng.eclipsebuilder/eclipse/helper.xml file
246	 */
247	private static String[] CONFIG_DESCRIPTIONS;
248
249	/**
250	 * The list of known Eclipse components.
251	 */
252	private final static String[] ECLIPSE_COMPONENTS = {
253		"org.eclipse.ant", //$NON-NLS-1$
254		"org.eclipse.compare", //$NON-NLS-1$
255		"org.eclipse.core", //$NON-NLS-1$
256		"org.eclipse.help", //$NON-NLS-1$
257		"org.eclipse.jdt.core", //$NON-NLS-1$
258		"org.eclipse.jdt.debug", //$NON-NLS-1$
259		"org.eclipse.jdt.text", //$NON-NLS-1$
260		"org.eclipse.jdt.ui", //$NON-NLS-1$
261		"org.eclipse.jface", //$NON-NLS-1$
262		"org.eclipse.osgi", //$NON-NLS-1$
263		"org.eclipse.pde.api.tools", //$NON-NLS-1$
264		"org.eclipse.pde.ui", //$NON-NLS-1$
265		"org.eclipse.swt", //$NON-NLS-1$
266		"org.eclipse.team", //$NON-NLS-1$
267		"org.eclipse.ua", //$NON-NLS-1$
268		"org.eclipse.ui" //$NON-NLS-1$
269	};
270	private static String[] KNOWN_COMPONENTS = ECLIPSE_COMPONENTS;
271
272
273	    // Store debug info
274	final static StringWriter DEBUG_STR_WRITER;
275	final static PrintWriter DEBUG_WRITER;
276	static {
277		if (DEBUG) {
278			DEBUG_STR_WRITER= new StringWriter();
279			DEBUG_WRITER= new PrintWriter(DEBUG_STR_WRITER);
280		} else {
281			DEBUG_STR_WRITER= null;
282			DEBUG_WRITER= null;
283		}
284	}
285
286    // Store log info
287    final static StringWriter LOG_STR_WRITER = new StringWriter();
288    final static LogWriter LOG_WRITER = new LogWriter();
289    static class LogWriter extends PrintWriter {
290		long[] starts = new long[10];
291		long[] times = new long[10];
292    	StringBuffer[] buffers = new StringBuffer[10];
293    	int depth = -1, max = -1;
294    	public LogWriter() {
295	        super(LOG_STR_WRITER);
296        }
297		void starts(String log) {
298    		if (++this.depth >= this.buffers.length) {
299    			System.arraycopy(this.times, 0, this.times = new long[this.depth+10], 0, this.depth);
300    			System.arraycopy(this.buffers, 0, this.buffers= new StringBuffer[this.depth+10], 0, this.depth);
301    		}
302    		StringBuffer buffer = this.buffers[this.depth];
303    		if (this.buffers[this.depth] == null) buffer = this.buffers[this.depth] = new StringBuffer();
304    		buffer.append(log);
305    		this.starts[this.depth] = System.currentTimeMillis();
306    		if (this.depth > this.max) this.max = this.depth;
307    	}
308		void ends(String log) {
309			if (this.depth < 0)
310				throw new RuntimeException("Invalid call to ends (missing corresponding starts call)!"); //$NON-NLS-1$
311    		this.buffers[this.depth].append(log);
312    		if (this.depth > 0) {
313    			this.times[this.depth] += System.currentTimeMillis() - this.starts[this.depth];
314    			this.depth--;
315    			return;
316    		}
317    		for (int i=0; i<this.max; i++) {
318	    		print(this.buffers[i].toString());
319	    		print(" ( in "); //$NON-NLS-1$
320	    		print(this.times[this.depth]);
321    			println("ms)"); //$NON-NLS-1$
322    		}
323    		this.depth = this.max = -1;
324			this.starts = new long[10];
325			this.times = new long[10];
326    		this.buffers = new StringBuffer[10];
327    	}
328		public String toString() {
329	        return LOG_STR_WRITER.toString();
330        }
331    }
332
333	// Data storage from queries
334	static String LAST_CURRENT_BUILD, LAST_BASELINE_BUILD;
335	private static int BUILDS_LENGTH;
336	private static String[] SCENARII;
337	private static String[] COMMENTS;
338
339    //---- private implementation
340
341	/**
342     * Private constructor to block instance creation.
343     */
344    private DB_Results() {
345    	// empty implementation
346    }
347
348    synchronized static DB_Results getDefault() {
349        if (fgDefault == null) {
350            fgDefault= new DB_Results();
351            fgDefault.connect();
352            if (PerformanceTestPlugin.getDefault() == null) {
353            	// not started as plugin
354	            Runtime.getRuntime().addShutdownHook(
355	                new Thread() {
356	                    public void run() {
357	                    	shutdown();
358	                    }
359	                }
360	            );
361            }
362        } else if (fgDefault.fSQL == null) {
363        	fgDefault.connect();
364        }
365        return fgDefault;
366    }
367
368    public static void shutdown() {
369        if (fgDefault != null) {
370            fgDefault.disconnect();
371            fgDefault= null;
372            BUILDS = null;
373            LAST_BASELINE_BUILD = null;
374            LAST_CURRENT_BUILD = null;
375            DIMENSIONS = null;
376            CONFIGS = null;
377            COMPONENTS = null;
378            SCENARII = null;
379            COMMENTS = null;
380            DB_VERSION = null;
381            DB_VERSION_REF = null;
382            DEFAULT_DIM =null;
383            DEFAULT_DIM_INDEX = -1;
384            RESULTS_DIMENSIONS = null;
385            VMS = null;
386            CONFIG_DESCRIPTIONS = null;
387            KNOWN_COMPONENTS = ECLIPSE_COMPONENTS;
388        }
389        if (DEBUG) {
390        	DEBUG_WRITER.println("DB.shutdown"); //$NON-NLS-1$
391        	System.out.println(DEBUG_STR_WRITER.toString());
392        }
393        if (LOG) {
394        	System.out.println(LOG_STR_WRITER.toString());
395        }
396    }
397
398/**
399 * Return the build id from a given name.
400 *
401 * @param name The build name (eg. I20070615-1200)
402 * @return The id of the build (ie. the index in the {@link #BUILDS} list)
403 */
404static int getBuildId(String name) {
405	if (BUILDS == null) return -1;
406	return Arrays.binarySearch(BUILDS, name, Util.BUILD_DATE_COMPARATOR);
407}
408
409/**
410 * Return the build name from a given id.
411 *
412 * @param id The build id
413 * @return The name of the build (eg. I20070615-1200)
414 */
415static String getBuildName(int id) {
416	if (BUILDS == null) return null;
417	return BUILDS[id];
418}
419
420/**
421 * Returns all the builds names read from the database.
422 *
423 * @return The list of all builds names matching the scenario pattern used while reading data
424 */
425public static String[] getBuilds() {
426	if (BUILDS == null) {
427		queryAllVariations("%"); //$NON-NLS-1$
428	}
429	if (BUILDS_LENGTH == 0) return EMPTY_LIST;
430	String[] builds = new String[BUILDS_LENGTH];
431	System.arraycopy(BUILDS, 0, builds, 0, BUILDS_LENGTH);
432	return builds;
433}
434
435/**
436 * Returns the number of builds stored int the database.
437 *
438 * @return The number of builds stored in the database.
439 */
440public static int getBuildsNumber() {
441	if (BUILDS == null) {
442		queryAllVariations("%"); //$NON-NLS-1$
443	}
444	return BUILDS_LENGTH;
445}
446
447/**
448 * Get component name from a scenario.
449 *
450 * @param scenarioName The name of the scenario
451 * @return The component name
452 */
453static String getComponentNameFromScenario(String scenarioName) {
454	int length = KNOWN_COMPONENTS.length;
455	for (int i=0; i<length; i++) {
456		if (scenarioName.startsWith(KNOWN_COMPONENTS[i])) {
457			return KNOWN_COMPONENTS[i];
458		}
459	}
460	StringTokenizer tokenizer = new StringTokenizer(scenarioName, ".");
461	StringBuffer buffer = new StringBuffer(tokenizer.nextToken());
462	if (tokenizer.hasMoreTokens()) {
463		buffer.append('.');
464		buffer.append(tokenizer.nextToken());
465		if (tokenizer.hasMoreTokens()) {
466			buffer.append('.');
467			buffer.append(tokenizer.nextToken());
468		}
469	}
470	String componentName = buffer.toString();
471	System.err.println(scenarioName+" does not belongs to a known Eclipse component. So use scenario prefix "+componentName+" as component name by default and add it to the know components"); //$NON-NLS-1$
472	System.arraycopy(KNOWN_COMPONENTS, 0, KNOWN_COMPONENTS = new String[length+1], 0, length);
473	KNOWN_COMPONENTS[length] = componentName;
474	return componentName;
475}
476
477/**
478 * Get all components read from database.
479 *
480 * @return A list of component names matching the given pattern
481 */
482public static String[] getComponents() {
483	if (COMPONENTS == null) return EMPTY_LIST;
484	int length = COMPONENTS.length;
485	String[] components = new String[length];
486	System.arraycopy(COMPONENTS, 0, components, 0, length);
487	return components;
488}
489
490/**
491 * Return the name of the configuration from the given id.
492 *
493 * @param id The index of the configuration in the stored list.
494 * @return The name of the configuration (eg. eclipseperflnx1_R3.3)
495 */
496static String getConfig(int id) {
497	return CONFIGS[id];
498}
499
500/**
501 * Get all configurations read from the database.
502 *
503 * @return A list of configuration names
504 */
505public static String[] getConfigs() {
506	if (CONFIGS == null) return EMPTY_LIST;
507	int length = CONFIGS.length;
508	String[] configs = new String[length];
509	System.arraycopy(CONFIGS, 0, configs, 0, length);
510	return configs;
511}
512
513/**
514 * Set the default dimension used for performance results.
515 */
516public static void setConfigs(String[] configs) {
517	CONFIGS = configs;
518}
519
520/**
521 * Get all configurations read from the database.
522 *
523 * @return A list of configuration names
524 */
525public static String[] getConfigDescriptions() {
526	if (CONFIG_DESCRIPTIONS == null) {
527		if (CONFIGS == null) return null;
528		int length = CONFIGS.length;
529		CONFIG_DESCRIPTIONS = new String[length];
530		String[][] configDescriptors = PerformanceTestPlugin.getConfigDescriptors();
531		int cdLength = configDescriptors.length;
532		for (int i = 0; i < length; i++) {
533			boolean found = false;
534			for (int j = 0; j < cdLength; j++) {
535				if (configDescriptors[j][0].equals(CONFIGS[i])) {
536			        CONFIG_DESCRIPTIONS[i] = configDescriptors[j][1];
537			        found = true;
538			        break;
539				}
540			}
541			if (!found) {
542				String kind = CONFIGS[i].indexOf("epwin") < 0 ? "Linux" : "Win XP";
543				CONFIG_DESCRIPTIONS[i] = kind+" perf test box "+CONFIGS[i].substring(5);
544			}
545        }
546	}
547	int length = CONFIG_DESCRIPTIONS.length;
548	String[] descriptions = new String[length];
549	System.arraycopy(CONFIG_DESCRIPTIONS, 0, descriptions, 0, length);
550	return descriptions;
551}
552
553/**
554 * Set the default dimension used for performance results.
555 */
556public static void setConfigDescriptions(String[] descriptions) {
557	CONFIG_DESCRIPTIONS = descriptions;
558}
559
560/**
561 * Get all dimensions read from the database.
562 *
563 * @return A list of dimensions.
564 */
565public static Dim[] getDimensions() {
566	if (DIMENSIONS == null) return NO_DIMENSION;
567	int length = DIMENSIONS.length;
568	Dim[] dimensions = new Dim[length];
569	for (int i = 0; i < length; i++) {
570		Dimension dimension = PerformanceTestPlugin.getDimension(DIMENSIONS[i]);
571		if (dimension == null) {
572			throw new RuntimeException("There is an unsupported dimension stored in the database: " +DIMENSIONS[i]);
573		}
574		dimensions[i] = (Dim) dimension;
575    }
576	return dimensions;
577}
578
579/**
580 * Return the default dimension used for performance results.
581 *
582 * @return The {@link Dim default dimension}.
583 */
584public static Dim getDefaultDimension() {
585	if (DEFAULT_DIM == null) {
586		DEFAULT_DIM = (Dim) PerformanceTestPlugin.getDefaultDimension();
587	}
588	return DEFAULT_DIM;
589}
590
591/**
592 * Set the default dimension used for performance results.
593 */
594public static void setDefaultDimension(String dim) {
595	DEFAULT_DIM = (Dim) PerformanceTestPlugin.getDimension(dim);
596	if (DIMENSIONS != null) {
597		DEFAULT_DIM_INDEX = Arrays.binarySearch(DIMENSIONS, DEFAULT_DIM.getId());
598	}
599}
600
601public static Dim[] getResultsDimensions() {
602	if (RESULTS_DIMENSIONS == null) {
603		Dimension[] resultsDimensions = PerformanceTestPlugin.getResultsDimensions();
604		int length = resultsDimensions.length;
605		RESULTS_DIMENSIONS = new Dim[length];
606		for (int i = 0; i < length; i++) {
607			RESULTS_DIMENSIONS[i] = (Dim) resultsDimensions[i];
608		}
609	}
610	return RESULTS_DIMENSIONS;
611}
612
613/**
614 * Set the default dimension used for performance results.
615 */
616public static void setResultsDimensions(String[] dimensions) {
617	int length = dimensions.length;
618	RESULTS_DIMENSIONS = new Dim[length];
619	for (int i = 0; i < length; i++) {
620		RESULTS_DIMENSIONS[i] = (Dim) PerformanceTestPlugin.getDimension(dimensions[i]);
621	}
622}
623
624/**
625 * Return the default dimension used for performance results.
626 *
627 * @return The {@link Dim default dimension}.
628 */
629public static int getDefaultDimensionIndex() {
630	if (DEFAULT_DIM == null || DEFAULT_DIM_INDEX == -1) {
631		getDefaultDimension(); // init default dimension
632		getDimensions(); // init dimensions
633		DEFAULT_DIM_INDEX = Arrays.binarySearch(DIMENSIONS, DEFAULT_DIM.getId());
634	}
635	return DEFAULT_DIM_INDEX;
636}
637
638/**
639 * Return the ID of the last baseline build before the given date.
640 *
641 * @param date The date the baseline must be run before. If <code>null</code>
642 * 	return the last baseline build stored in the DB.
643 *
644 * @return the ID of the last baseline build before the given date or
645 * 	<code>null</code> if none was run before it...
646 */
647public static String getLastBaselineBuild(String date) {
648	if (BUILDS == null) {
649		queryAllVariations("%"); //$NON-NLS-1$
650	}
651	if (date == null) {
652		if (LAST_BASELINE_BUILD == null) {
653			return BUILDS[0];
654		}
655		return LAST_BASELINE_BUILD;
656	}
657	String lastBaselineBuild = null;
658	for (int i=0; i<BUILDS_LENGTH; i++) {
659		String build = BUILDS[i];
660		if (build.startsWith(DB_VERSION_REF)) {
661			String buildDate = build.substring(build.indexOf('_')+1);
662			if (buildDate.compareTo(date) < 0) {
663				if (lastBaselineBuild == null || build.compareTo(lastBaselineBuild) > 0) {
664					lastBaselineBuild = build;
665				}
666			}
667		}
668	}
669	if (lastBaselineBuild == null) {
670		return BUILDS[0];
671	}
672	return lastBaselineBuild;
673}
674
675/**
676 * Return the ID of the last baseline build.
677 *
678 * @return the ID of the last baseline build.
679 */
680public static String getLastCurrentBuild() {
681	if (BUILDS == null) {
682		queryAllVariations("%"); //$NON-NLS-1$
683	}
684	return LAST_CURRENT_BUILD;
685}
686
687/**
688 * Returns all the scenarios names read from the database.
689 *
690 * @return The list of all scenarios matching the pattern for a given build.
691 * @see #internalQueryBuildScenarios(String, String)
692 */
693public static List getScenarios() {
694	return Arrays.asList(SCENARII);
695}
696
697/**
698 * Init the constants if necessary.
699 */
700public static void initDbContants() {
701	if (DB_LOCATION == null) {
702		DB_LOCATION = PerformanceTestPlugin.getDBLocation();
703		if (DB_LOCATION == null) {
704			new RuntimeException("Cannot connect to the DB without a location!");
705		}
706	}
707	if (DB_NAME == null) {
708		DB_NAME = PerformanceTestPlugin.getDBName();
709		if (DB_NAME == null) {
710			new RuntimeException("Cannot connect to the DB without a name!");
711		}
712	}
713	if (DB_VERSION == null) {
714		DB_VERSION = "v" + DB_NAME.substring(DB_NAME.length()-2);
715		DB_VERSION_REF = "R-3."+(Character.digit(DB_NAME.charAt(DB_NAME.length()-1), 10)-1);
716	}
717}
718
719/**
720 * Get all scenarios read from database.
721 *
722 * @return A list of all scenario names matching the default pattern
723 */
724public static Map queryAllScenarios() {
725	return getDefault().internalQueryBuildScenarios("%", null); //$NON-NLS-1$
726}
727
728/**
729 * Get all scenarios read from database matching a given pattern.
730 * Note that all scenarios are returned if the pattern is <code>null</code>.
731 *
732 * @param scenarioPattern The pattern of the requested scenarios
733 * @return A map of all scenarios matching the given pattern.
734 * 	The map keys are component names and values are the scenarios list for
735 * 	each component.
736 */
737static Map queryAllScenarios(String scenarioPattern) {
738	String pattern = scenarioPattern==null ? "%" : scenarioPattern; //$NON-NLS-1$
739	return getDefault().internalQueryBuildScenarios(pattern, null);
740}
741
742/**
743 * Get all scenarios read from database matching a given pattern.
744 * Note that all scenarios are returned if the pattern is <code>null</code>.
745 *
746 * @param scenarioPattern The pattern of the requested scenarios
747 * @param buildName The build name
748 * @return A list of scenario names matching the given pattern
749 */
750static Map queryAllScenarios(String scenarioPattern, String buildName) {
751	return getDefault().internalQueryBuildScenarios(scenarioPattern, buildName);
752}
753
754/**
755 * Get all variations read from database matching a given configuration pattern.
756 *
757 * @param configPattern The pattern of the requested configurations
758 */
759static void queryAllVariations(String configPattern) {
760	getDefault().internalQueryAllVariations(configPattern);
761}
762
763/**
764 * Get all summaries from DB for a given scenario and configuration pattern
765 *
766 * @param scenarioResults The scenario results where to store data
767 * @param configPattern The configuration pattern concerned by the query
768 * @param builds All builds to get summaries, if <code>null</code>, then all DB
769 * 	builds will be concerned.
770 */
771static void queryScenarioSummaries(ScenarioResults scenarioResults, String configPattern, String[] builds) {
772	getDefault().internalQueryScenarioSummaries(scenarioResults, configPattern, builds);
773}
774
775/**
776 * Query and store all values for given scenario results
777 *
778 * @param scenarioResults The scenario results where the values has to be put
779 * @param configPattern The pattern of the configuration concerned by the query
780 * @param buildName Name of the last build on which data were stored locally
781 *
782*/
783static void queryScenarioValues(ScenarioResults scenarioResults, String configPattern, String buildName) {
784	getDefault().internalQueryScenarioValues(scenarioResults, configPattern, buildName);
785}
786
787/**
788 * dbloc=						embed in home directory
789 * dbloc=/tmp/performance			embed given location
790 * dbloc=net://localhost			connect to local server
791 * dbloc=net://www.eclipse.org	connect to remove server
792 */
793private void connect() {
794
795	if (this.fConnection != null || !DB_CONNECTION)
796		return;
797
798	if (DEBUG) DriverManager.setLogWriter(new PrintWriter(System.out));
799
800	// Init DB location and name if not already done
801	if (DB_LOCATION == null) {
802		initDbContants();
803	}
804
805	String url = null;
806	java.util.Properties info = new java.util.Properties();
807
808	if (DEBUG) {
809		DEBUG_WRITER.println();
810		DEBUG_WRITER.println("==========================================================="); //$NON-NLS-1$
811		DEBUG_WRITER.println("Database debug information stored while processing"); //$NON-NLS-1$
812	}
813	if (LOG) {
814		LOG_WRITER.println();
815		LOG_WRITER.println("==========================================================="); //$NON-NLS-1$
816		LOG_WRITER.println("Database log information stored while processing"); //$NON-NLS-1$
817	}
818
819	this.fDBType = DERBY; // assume we are using Derby
820	try {
821		if (DB_LOCATION.startsWith("net://")) { //$NON-NLS-1$
822			// remote
823//			fIsEmbedded = false;
824			// connect over network
825			if (DEBUG)
826				DEBUG_WRITER.println("Trying to connect over network..."); //$NON-NLS-1$
827			Class.forName("com.ibm.db2.jcc.DB2Driver"); //$NON-NLS-1$
828			info.put("user", PerformanceTestPlugin.getDBUser()); //$NON-NLS-1$
829			info.put("password", PerformanceTestPlugin.getDBPassword()); //$NON-NLS-1$
830			info.put("retrieveMessagesFromServerOnGetMessage", "true"); //$NON-NLS-1$ //$NON-NLS-2$
831			info.put("create", "true"); //$NON-NLS-1$ //$NON-NLS-2$
832			url = DB_LOCATION + '/' + DB_NAME;
833		} else if (DB_LOCATION.startsWith("//")) { //$NON-NLS-1$
834			// remote
835//			fIsEmbedded = false;
836			// connect over network
837			if (DEBUG)
838				DEBUG_WRITER.println("Trying to connect over network..."); //$NON-NLS-1$
839			Class.forName("org.apache.derby.jdbc.ClientDriver"); //$NON-NLS-1$
840			info.put("user", PerformanceTestPlugin.getDBUser()); //$NON-NLS-1$
841			info.put("password", PerformanceTestPlugin.getDBPassword()); //$NON-NLS-1$
842			info.put("create", "true"); //$NON-NLS-1$ //$NON-NLS-2$
843			url = DB_LOCATION + '/' + DB_NAME;
844		} else {
845			// workaround for Derby issue:
846			// http://nagoya.apache.org/jira/browse/DERBY-1
847			if ("Mac OS X".equals(System.getProperty("os.name"))) //$NON-NLS-1$//$NON-NLS-2$
848				System.setProperty("derby.storage.fileSyncTransactionLog", "true"); //$NON-NLS-1$ //$NON-NLS-2$
849
850			// embedded
851			try {
852				Class.forName("org.apache.derby.jdbc.EmbeddedDriver"); //$NON-NLS-1$
853//				fIsEmbedded = true;
854			} catch (ClassNotFoundException e) {
855				Class.forName("com.ihost.cs.jdbc.CloudscapeDriver"); //$NON-NLS-1$
856				this.fDBType = CLOUDSCAPE;
857			}
858			if (DEBUG)
859				DEBUG_WRITER.println("Loaded embedded " + this.fDBType); //$NON-NLS-1$
860			File f;
861			if (DB_LOCATION.length() == 0) {
862				String user_home = System.getProperty("user.home"); //$NON-NLS-1$
863				if (user_home == null)
864					return;
865				f = new File(user_home, this.fDBType);
866			} else
867				f = new File(DB_LOCATION);
868			url = new File(f, DB_NAME).getAbsolutePath();
869			info.put("user", PerformanceTestPlugin.getDBUser()); //$NON-NLS-1$
870			info.put("password", PerformanceTestPlugin.getDBPassword()); //$NON-NLS-1$
871			info.put("create", "true"); //$NON-NLS-1$ //$NON-NLS-2$
872		}
873		try {
874			this.fConnection = DriverManager.getConnection("jdbc:" + this.fDBType + ":" + url, info); //$NON-NLS-1$ //$NON-NLS-2$
875		} catch (SQLException e) {
876			if ("08001".equals(e.getSQLState()) && DERBY.equals(this.fDBType)) { //$NON-NLS-1$
877				if (DEBUG)
878					DEBUG_WRITER.println("DriverManager.getConnection failed; retrying for cloudscape"); //$NON-NLS-1$
879				// try Cloudscape
880				this.fDBType = CLOUDSCAPE;
881				this.fConnection = DriverManager.getConnection("jdbc:" + this.fDBType + ":" + url, info); //$NON-NLS-1$ //$NON-NLS-2$
882			} else
883				throw e;
884		}
885		if (DEBUG)
886			DEBUG_WRITER.println("connect succeeded!"); //$NON-NLS-1$
887
888		this.fConnection.setAutoCommit(false);
889		this.fSQL = new SQL_Results(this.fConnection);
890		this.fConnection.commit();
891
892	} catch (SQLException ex) {
893		PerformanceTestPlugin.logError(ex.getMessage());
894
895	} catch (ClassNotFoundException e) {
896		PerformanceTestPlugin.log(e);
897	}
898}
899
900private void disconnect() {
901	if (DEBUG)
902		DEBUG_WRITER.println("disconnecting from DB"); //$NON-NLS-1$
903	if (this.fSQL != null) {
904		try {
905			this.fSQL.dispose();
906		} catch (SQLException e1) {
907			PerformanceTestPlugin.log(e1);
908		}
909		this.fSQL = null;
910	}
911	if (this.fConnection != null) {
912		try {
913			this.fConnection.commit();
914		} catch (SQLException e) {
915			PerformanceTestPlugin.log(e);
916		}
917		try {
918			this.fConnection.close();
919		} catch (SQLException e) {
920			PerformanceTestPlugin.log(e);
921		}
922		this.fConnection = null;
923	}
924
925	/*
926	if (fIsEmbedded) {
927		try {
928			DriverManager.getConnection("jdbc:" + fDBType + ":;shutdown=true"); //$NON-NLS-1$ //$NON-NLS-2$
929		} catch (SQLException e) {
930			String message = e.getMessage();
931			if (message.indexOf("system shutdown.") < 0) //$NON-NLS-1$
932				e.printStackTrace();
933		}
934	}
935	*/
936}
937
938/*
939 * Return the index of the given configuration in the stored list.
940 */
941private int getConfigId(String config) {
942	if (CONFIGS == null) return -1;
943	return Arrays.binarySearch(CONFIGS, config);
944}
945
946SQL_Results getSQL() {
947    return this.fSQL;
948}
949
950/*
951 * Query all comments from database
952 */
953private void internalQueryAllComments() {
954	if (this.fSQL == null) return;
955	if (COMMENTS != null) return;
956	long start = System.currentTimeMillis();
957	if (DEBUG) DEBUG_WRITER.print("		[DB query all comments..."); //$NON-NLS-1$
958	ResultSet result = null;
959	try {
960		String[] comments = null;
961		result = this.fSQL.queryAllComments();
962		while (result.next()) {
963			int commentID = result.getInt(1);
964			// Ignore kind as there's only one
965			// int commentKind = result.getInt(2);
966			String comment = result.getString(3);
967			if (comments == null) {
968				comments = new String[commentID+10];
969			} else if (commentID >= comments.length) {
970				int length = comments.length;
971				System.arraycopy(comments, 0, comments = new String[commentID+10], 0, length);
972			}
973			comments[commentID] = comment;
974		}
975		COMMENTS = comments;
976	} catch (SQLException e) {
977		PerformanceTestPlugin.log(e);
978	} finally {
979		if (result != null) {
980			try {
981				result.close();
982			} catch (SQLException e1) {
983				// ignored
984			}
985		}
986		if (DEBUG) DEBUG_WRITER.println("done in " + (System.currentTimeMillis() - start) + "ms]"); //$NON-NLS-1$ //$NON-NLS-2$
987	}
988}
989
990/*
991 * Query all variations. This method stores all config and build names.
992 */
993private void internalQueryAllVariations(String configPattern) {
994	if (this.fSQL == null) return;
995	if (BUILDS != null) return;
996	long start = System.currentTimeMillis();
997	if (DEBUG) {
998		DEBUG_WRITER.print("	- DB query all variations for configuration pattern: "+configPattern); //$NON-NLS-1$
999		DEBUG_WRITER.print("..."); //$NON-NLS-1$
1000	}
1001	ResultSet result = null;
1002	try {
1003		CONFIGS = null;
1004		BUILDS = null;
1005		BUILDS_LENGTH = 0;
1006		result = this.fSQL.queryAllVariations(configPattern);
1007		while (result.next()) {
1008			String variation = result.getString(1); //  something like "||build=I20070615-1200||config=eclipseperfwin2_R3.3||jvm=sun|"
1009			StringTokenizer tokenizer = new StringTokenizer(variation, "=|"); //$NON-NLS-1$
1010			tokenizer.nextToken(); 												// 'build'
1011			storeBuildName(tokenizer.nextToken());				// 'I20070615-1200'
1012			tokenizer.nextToken();												// 'config'
1013			storeConfig(tokenizer.nextToken()); 	// 'eclipseperfwin2_R3.3'
1014			tokenizer.nextToken();												// 'jvm'
1015			storeVm(tokenizer.nextToken());					// 'sun'
1016		}
1017		if (BUILDS_LENGTH == 0) {
1018			BUILDS = EMPTY_LIST;
1019		}
1020	} catch (SQLException e) {
1021		PerformanceTestPlugin.log(e);
1022	} finally {
1023		if (result != null) {
1024			try {
1025				result.close();
1026			} catch (SQLException e1) {
1027				// ignored
1028			}
1029		}
1030		if (DEBUG) DEBUG_WRITER.println("done in " + (System.currentTimeMillis() - start) + "ms]"); //$NON-NLS-1$ //$NON-NLS-2$
1031	}
1032}
1033
1034private Map internalQueryBuildScenarios(String scenarioPattern, String buildName) {
1035	if (this.fSQL == null) return null;
1036	long start = System.currentTimeMillis();
1037	if (DEBUG) {
1038		DEBUG_WRITER.print("	- DB query all scenarios"); //$NON-NLS-1$
1039		if (scenarioPattern != null) DEBUG_WRITER.print(" with pattern "+scenarioPattern); //$NON-NLS-1$
1040		if (buildName != null) DEBUG_WRITER.print(" for build: "+buildName); //$NON-NLS-1$
1041	}
1042	ResultSet result = null;
1043	Map allScenarios = new HashMap();
1044	try {
1045		if (buildName == null) {
1046			result = this.fSQL.queryBuildAllScenarios(scenarioPattern);
1047		} else {
1048			result = this.fSQL.queryBuildScenarios(scenarioPattern, buildName);
1049		}
1050		int previousId = -1;
1051		List scenarios = null;
1052		List scenariosNames = new ArrayList();
1053		for (int i = 0; result.next(); i++) {
1054			int id = result.getInt(1);
1055			String name = result.getString(2);
1056			scenariosNames.add(name);
1057			String shortName = result.getString(3);
1058			int component_id = storeComponent(getComponentNameFromScenario(name));
1059			if (component_id != previousId) {
1060				allScenarios.put(COMPONENTS[component_id], scenarios = new ArrayList());
1061				previousId = component_id;
1062			}
1063			scenarios.add(new ScenarioResults(id, name, shortName));
1064		}
1065		SCENARII = new String[scenariosNames.size()];
1066		scenariosNames.toArray(SCENARII);
1067	} catch (SQLException e) {
1068		PerformanceTestPlugin.log(e);
1069	} finally {
1070		if (result != null) {
1071			try {
1072				result.close();
1073			} catch (SQLException e1) { // ignored
1074			}
1075		}
1076		if (DEBUG) DEBUG_WRITER.println("done in " + (System.currentTimeMillis() - start) + "ms]"); //$NON-NLS-1$ //$NON-NLS-2$
1077	}
1078	return allScenarios;
1079}
1080
1081private void internalQueryScenarioValues(ScenarioResults scenarioResults, String configPattern, String buildName) {
1082	if (this.fSQL == null) return;
1083	if (DEBUG) {
1084		DEBUG_WRITER.print("	- DB query all data points for config pattern: "+configPattern+" for scenario: " + scenarioResults.getShortName()); //$NON-NLS-1$ //$NON-NLS-2$
1085		if (buildName != null) DEBUG_WRITER.print(" for build: "+buildName); //$NON-NLS-1$
1086	}
1087	internalQueryAllVariations(configPattern); // need to read all variations to have all build names
1088	ResultSet result = null;
1089	try {
1090		int count = 0;
1091
1092		result = buildName == null
1093			?	this.fSQL.queryScenarioDataPoints(configPattern, scenarioResults.getId())
1094			:	this.fSQL.queryScenarioBuildDataPoints(configPattern, scenarioResults.getId(), buildName);
1095		while (result.next()) {
1096			int dp_id = result.getInt(1);
1097			int step = result.getInt(2);
1098			String variation = result.getString(3); //  something like "|build=I20070615-1200||config=eclipseperfwin2_R3.3||jvm=sun|"
1099			StringTokenizer tokenizer = new StringTokenizer(variation, "=|"); //$NON-NLS-1$
1100			tokenizer.nextToken(); 													// 'build'
1101			int build_id = getBuildId(tokenizer.nextToken());		// 'I20070615-1200'
1102			tokenizer.nextToken();													// 'config'
1103			int config_id = getConfigId(tokenizer.nextToken()); 		// 'eclipseperflnx3'
1104			ResultSet rs2 = this.fSQL.queryDimScalars(dp_id);
1105			while (rs2.next()) {
1106				int dim_id = rs2.getInt(1);
1107				storeDimension(dim_id);
1108				BigDecimal decimalValue = rs2.getBigDecimal(2);
1109				long value = decimalValue.longValue();
1110				if (build_id >= 0) { // build id may be negative (i.e. not stored in the array) if new run starts while we're getting results
1111					scenarioResults.setValue(build_id, dim_id, config_id, step, value);
1112				}
1113				count++;
1114			}
1115		}
1116		if (LOG) LOG_WRITER.ends("		-> " + count + " values read");  //$NON-NLS-1$ //$NON-NLS-2$
1117	} catch (SQLException e) {
1118		PerformanceTestPlugin.log(e);
1119	} finally {
1120		if (result != null) {
1121			try {
1122				result.close();
1123			} catch (SQLException e1) {
1124				// ignored
1125			}
1126		}
1127	}
1128}
1129
1130private void internalQueryScenarioSummaries(ScenarioResults scenarioResults, String config, String[] builds) {
1131	if (this.fSQL == null) return;
1132	long start = System.currentTimeMillis();
1133	if (DEBUG) {
1134		DEBUG_WRITER.print("	- DB query all summaries for scenario '"+scenarioResults.getShortName()+"' of '"+config+"' config"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1135	}
1136	internalQueryAllComments();
1137	ResultSet result = null;
1138	try {
1139		int scenarioID = scenarioResults.getId();
1140		// First try to get summaries of elapsed process dimension
1141		result = this.fSQL.queryScenarioSummaries(scenarioID, config, builds);
1142		while (result.next()) {
1143			String variation = result.getString(1); //  something like "|build=I20070615-1200||config=eclipseperfwin2_R3.3||jvm=sun|"
1144			int summaryKind = result.getShort(2);
1145			int comment_id = result.getInt(3);
1146			int dim_id = result.getInt(4);
1147			if (dim_id != 0) storeDimension(dim_id);
1148			StringTokenizer tokenizer = new StringTokenizer(variation, "=|"); //$NON-NLS-1$
1149			tokenizer.nextToken(); 													// 'build'
1150			String buildName = tokenizer.nextToken();					// 'I20070615-1200'
1151			tokenizer.nextToken();													// 'config'
1152			int config_id = getConfigId(tokenizer.nextToken()); 		// 'eclipseperflnx3'
1153			int build_id = getBuildId(buildName);
1154			if (build_id >= 0) {
1155				scenarioResults.setInfos(config_id, build_id, dim_id==0?-1:summaryKind, COMMENTS[comment_id]);
1156			}
1157		}
1158	} catch (SQLException e) {
1159		PerformanceTestPlugin.log(e);
1160	} finally {
1161		if (result != null) {
1162			try {
1163				result.close();
1164			} catch (SQLException e1) {
1165				// ignored
1166			}
1167		}
1168		if (DEBUG) DEBUG_WRITER.println("done in " + (System.currentTimeMillis() - start) + "ms]"); //$NON-NLS-1$ //$NON-NLS-2$
1169	}
1170}
1171
1172/*
1173 * Store a build name in the dynamic list.
1174 * The list is sorted alphabetically.
1175 */
1176private int storeBuildName(String build) {
1177	boolean isVersion = build.startsWith(DB_BASELINE_PREFIX);
1178	if (BUILDS == null) {
1179		BUILDS = new String[1];
1180		BUILDS[BUILDS_LENGTH++] = build;
1181		if (isVersion) {
1182			LAST_BASELINE_BUILD = build;
1183		} else {
1184			LAST_CURRENT_BUILD = build;
1185		}
1186		return 0;
1187	}
1188	int idx = Arrays.binarySearch(BUILDS, build, Util.BUILD_DATE_COMPARATOR);
1189	if (idx >= 0) return idx;
1190	int index = -idx-1;
1191	int length = BUILDS.length;
1192	if (BUILDS_LENGTH == length) {
1193		String[] array = new String[length+1];
1194		if (index > 0) System.arraycopy(BUILDS, 0, array, 0, index);
1195		array[index] = build;
1196		if (index < length) {
1197			System.arraycopy(BUILDS, index, array, index+1, length-index);
1198		}
1199		BUILDS = array;
1200	}
1201	BUILDS_LENGTH++;
1202	if (isVersion) {
1203		if (LAST_BASELINE_BUILD == null || LAST_CURRENT_BUILD == null) {
1204			LAST_BASELINE_BUILD = build;
1205		} else {
1206			String buildDate = LAST_CURRENT_BUILD.substring(1, 9)+LAST_CURRENT_BUILD.substring(10, LAST_CURRENT_BUILD.length());
1207			String baselineDate = LAST_BASELINE_BUILD.substring(LAST_BASELINE_BUILD.indexOf('_')+1);
1208			if (build.compareTo(LAST_BASELINE_BUILD) > 0 && baselineDate.compareTo(buildDate) < 0) {
1209				LAST_BASELINE_BUILD = build;
1210			}
1211		}
1212	} else {
1213		if (LAST_CURRENT_BUILD == null || build.substring(1).compareTo(LAST_CURRENT_BUILD.substring(1)) >= 0) {
1214			LAST_CURRENT_BUILD = build;
1215		}
1216	}
1217	return index;
1218}
1219
1220/*
1221 * Store a configuration in the dynamic list.
1222 * The list is sorted alphabetically.
1223 */
1224private int storeConfig(String config) {
1225	if (CONFIGS== null) {
1226		CONFIGS= new String[1];
1227		CONFIGS[0] = config;
1228		return 0;
1229	}
1230	int idx = Arrays.binarySearch(CONFIGS, config);
1231	if (idx >= 0) return idx;
1232	int length = CONFIGS.length;
1233	System.arraycopy(CONFIGS, 0, CONFIGS = new String[length+1], 0, length);
1234	CONFIGS[length] = config;
1235	Arrays.sort(CONFIGS);
1236	return length;
1237}
1238
1239/*
1240 * Store a component in the dynamic list. The list is sorted alphabetically.
1241 * Note that the array is rebuilt each time a new component is discovered
1242 * as this does not happen so often (e.g. eclipse only has 10 components).
1243 */
1244private int storeComponent(String component) {
1245	if (COMPONENTS== null) {
1246		COMPONENTS= new String[1];
1247		COMPONENTS[0] = component;
1248		return 0;
1249	}
1250	int idx = Arrays.binarySearch(COMPONENTS, component);
1251	if (idx >= 0) return idx;
1252	int length = COMPONENTS.length;
1253	System.arraycopy(COMPONENTS, 0, COMPONENTS = new String[length+1], 0, length);
1254	COMPONENTS[length] = component;
1255	Arrays.sort(COMPONENTS);
1256	return length;
1257}
1258
1259/*
1260 * Store a dimension in the dynamic list. The list is sorted in ascending order.
1261 * Note that the array is rebuilt each time a new dimension is discovered
1262 * as this does not happen so often (e.g. eclipse only stores two dimensions).
1263 */
1264public static int storeDimension(int id) {
1265	if (DIMENSIONS == null) {
1266		DIMENSIONS = new int[1];
1267		DIMENSIONS[0] = id;
1268		return 0;
1269	}
1270	int idx = Arrays.binarySearch(DIMENSIONS, id);
1271	if (idx >= 0) return idx;
1272	int length = DIMENSIONS.length;
1273	System.arraycopy(DIMENSIONS, 0, DIMENSIONS = new int[length+1], 0, length);
1274	DIMENSIONS[length] = id;
1275	Arrays.sort(DIMENSIONS);
1276	return length;
1277}
1278
1279/*
1280 * Store a dimension in the dynamic list. The list is sorted alphabetically.
1281 * Note that the array is rebuilt each time a new dimension is discovered
1282 * as this does not happen so often (e.g. eclipse only stores two dimensions).
1283 */
1284private int storeVm(String vm) {
1285	if (VMS == null) {
1286		VMS = new String[1];
1287		VMS[0] = vm;
1288		return 0;
1289	}
1290	int idx = Arrays.binarySearch(VMS, vm);
1291	if (idx >= 0) return idx;
1292	int length = VMS.length;
1293	System.arraycopy(VMS, 0, VMS = new String[length+1], 0, length);
1294	VMS[length] = vm;
1295	Arrays.sort(VMS);
1296	return length;
1297}
1298
1299}
1300