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.DataInputStream;
14import java.io.DataOutputStream;
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.List;
18import org.eclipse.test.internal.performance.InternalDimensions;
19import org.eclipse.test.internal.performance.results.utils.Util;
20
21/**
22 * Class to handle results for an Eclipse performance test box
23 * (called a <i>configuration</i>).
24 *
25 * It gives access to results for each build on which this configuration has been run.
26 *
27 * @see BuildResults
28 */
29public class ConfigResults extends AbstractResults {
30	BuildResults baseline, current;
31	boolean baselined = false, valid = false;
32	double delta, error;
33
34public ConfigResults(AbstractResults parent, int id) {
35	super(parent, id);
36	this.name = parent.getPerformance().sortedConfigNames[id];
37	this.printStream = parent.printStream;
38}
39
40/*
41 * Complete results with additional database information.
42 */
43void completeResults(String[] builds) {
44	/*if (this.baseline == null || this.current == null) */initialize();
45	ScenarioResults scenarioResults = (ScenarioResults) this.parent;
46	DB_Results.queryScenarioSummaries(scenarioResults, this.name, builds);
47}
48
49/**
50 * Returns the baseline build name used to compare results with.
51 *
52 * @return The name of the baseline build
53 * @see #getBaselineBuildResults()
54 */
55public String getBaselineBuildName() {
56	if (this.baseline == null) {
57	    initialize();
58    }
59	return this.baseline.getName();
60}
61
62/**
63 * Returns the most recent baseline build results.
64 *
65 * @return The {@link BuildResults baseline build results}.
66 * @see BuildResults
67 */
68public BuildResults getBaselineBuildResults() {
69	if (this.baseline == null) {
70	    initialize();
71    }
72	return this.baseline;
73}
74
75/**
76 * Return the baseline build results run just before the given build name.
77 *
78 * @param buildName The build name
79 * @return The {@link BuildResults baseline results} preceding the given build name
80 * 	or <code>null</code> if none was found.
81 */
82public BuildResults getBaselineBuildResults(String buildName) {
83	if (this.baseline == null) {
84	    initialize();
85    }
86	int size = this.children.size();
87	String buildDate = Util.getBuildDate(buildName);
88	for (int i=size-1; i>=0; i--) {
89		BuildResults buildResults = (BuildResults) this.children.get(i);
90		if (buildResults.isBaseline() && buildResults.getDate().compareTo(buildDate) < 0) {
91			return buildResults;
92		}
93	}
94	return this.baseline;
95
96}
97
98/**
99 * Returns the most recent baseline build result value.
100 *
101 * @return The value of the most recent baseline build results.
102 */
103public double getBaselineBuildValue() {
104	if (this.baseline == null) {
105	    initialize();
106    }
107	return this.baseline.getValue();
108}
109
110/**
111 * Returns the configuration description (currently the box name).
112 *
113 * @return The configuration description (currently the box name).
114 */
115public String getDescription() {
116	return getPerformance().sortedConfigDescriptions[this.id];
117}
118
119/**
120 * Return the results for the given build name.
121 *
122 * @param buildName The build name
123 * @return The {@link BuildResults results} for the given build name
124 * 	or <code>null</code> if none was found.
125 */
126public BuildResults getBuildResults(String buildName) {
127	return (BuildResults) getResults(buildName);
128}
129
130/**
131 * Returns the build results matching a given pattern.
132 *
133 * @param buildPattern The pattern of searched builds
134 * @return The list of the builds which names match the given pattern.
135 * 	The list is ordered by build results date.
136 */
137public List getBuilds(String buildPattern) {
138	List builds = new ArrayList();
139	int size = size();
140	for (int i=0; i<size; i++) {
141		BuildResults buildResults = (BuildResults) this.children.get(i);
142		if (buildPattern == null || buildResults.match(buildPattern)) {
143			builds.add(buildResults);
144		}
145	}
146	return builds;
147}
148
149/**
150 * Returns the build results before a given build name.
151 *
152 * @param buildName Name of the last build (included)
153 * @return The list of the builds which precedes the given build name.
154 */
155public List getBuildsBefore(String buildName) {
156	String buildDate = Util.getBuildDate(buildName);
157	List builds = new ArrayList();
158	int size = size();
159	for (int i=0; i<size; i++) {
160		BuildResults buildResults = (BuildResults) this.children.get(i);
161		if (buildName == null || buildResults.getDate().compareTo(buildDate) <= 0) {
162			builds.add(buildResults);
163		}
164	}
165	return builds;
166}
167
168/**
169 * Returns a list of build results which names starts with one of the given prefixes.
170 *
171 * @param prefixes List of expected prefixes
172 * @return A list of builds which names start with one of the given patterns.
173 */
174public List getBuildsMatchingPrefixes(List prefixes) {
175	List builds = new ArrayList();
176	int size = size();
177	int length = prefixes.size();
178	for (int i=0; i<size; i++) {
179		AbstractResults buildResults = (AbstractResults) this.children.get(i);
180		String buildName = buildResults.getName();
181		for (int j=0; j<length; j++) {
182			if (buildName.startsWith((String)prefixes.get(j))) {
183				builds.add(buildResults);
184			}
185		}
186	}
187	return builds;
188}
189
190/**
191 * Get all results numbers for the max last builds.
192 *
193 * @param max The number of last builds to get numbers.
194 * @return An 2 dimensions array of doubles. At the first level of the array each slot
195 * 		represents one build. That means that the dimension of the array matches
196 * 		the given numbers as soon as there are enough builds in the database.
197 * <p>
198 * 		The slots of the second level are the numbers values:
199 * 	<ul>
200 * 		<li>{@link #BUILD_VALUE_INDEX}: the build value in milliseconds</li>
201 * 		<li>{@link #BASELINE_VALUE_INDEX}: the baseline value in milliseconds</li>
202 * 		<li>{@link #DELTA_VALUE_INDEX}: the difference between the build value and its more recent baseline</li>
203 * 		<li>{@link #DELTA_ERROR_INDEX}: the error made while computing the difference</li>
204 * 		<li>{@link #BUILD_ERROR_INDEX}: the error made while measuring the build value</li>
205 * 		<li>{@link #BASELINE_ERROR_INDEX}: the error made while measuring the baseline value</li>
206 * 	</ul>
207*/
208public double[][] getLastNumbers(int max) {
209
210	// Return null if no previous builds are expected
211	if (max <= 0) return null;
212
213	// Add numbers for each previous build
214	int size = size();
215	double[][] numbers = new double[Math.min(max, size)][];
216	int n = 0;
217	for (int i=size-1; i>=0 && n<max; i--) {
218		BuildResults buildResults = (BuildResults) this.children.get(i);
219		if (!buildResults.isBaseline()) {
220			numbers[n] = getNumbers(buildResults, getBaselineBuildResults(buildResults.getName()));
221			n++;
222		}
223	}
224
225	// Return the numbers
226	return numbers;
227}
228
229/**
230 * Returns interesting numbers for the current configuration.
231 *
232 * @return Values in an array of double:
233 * 	<ul>
234 * 		<li>{@link AbstractResults#BUILD_VALUE_INDEX}: the build value in milliseconds</li>
235 * 		<li>{@link AbstractResults#BASELINE_VALUE_INDEX}: the baseline value in milliseconds</li>
236 * 		<li>{@link AbstractResults#DELTA_VALUE_INDEX}: the difference between the build value and its more recent baseline</li>
237 * 		<li>{@link AbstractResults#DELTA_ERROR_INDEX}: the error made while computing the difference</li>
238 * 		<li>{@link AbstractResults#BUILD_ERROR_INDEX}: the error made while measuring the build value</li>
239 * 		<li>{@link AbstractResults#BASELINE_ERROR_INDEX}: the error made while measuring the baseline value</li>
240 * 	</ul>
241 */
242double[] getNumbers(BuildResults buildResults, BuildResults baselineResults) {
243	if (baselineResults == null) {
244		return null;
245	}
246	double[] values = new double[NUMBERS_LENGTH];
247	for (int i=0 ;i<NUMBERS_LENGTH; i++) {
248		values[i] = Double.NaN;
249	}
250	double buildValue = buildResults.getValue();
251	values[BUILD_VALUE_INDEX] = buildValue;
252	double baselineValue = baselineResults.getValue();
253	values[BASELINE_VALUE_INDEX] = baselineValue;
254	double buildDelta = (baselineValue - buildValue) / baselineValue;
255	values[DELTA_VALUE_INDEX] = buildDelta;
256	if (Double.isNaN(buildDelta)) {
257		return values;
258	}
259	long baselineCount = baselineResults.getCount();
260	long currentCount = buildResults.getCount();
261	if (baselineCount > 1 && currentCount > 1) {
262		double baselineError = baselineResults.getError();
263		double currentError = buildResults.getError();
264		values[BASELINE_ERROR_INDEX] = baselineError;
265		values[BUILD_ERROR_INDEX] = currentError;
266		values[DELTA_ERROR_INDEX] = Double.isNaN(baselineError)
267				? currentError / baselineValue
268				: Math.sqrt(baselineError*baselineError + currentError*currentError) / baselineValue;
269	}
270	return values;
271}
272
273/**
274 * Return the deviation value and its associated standard error for the default dimension
275 * (currently {@link InternalDimensions#ELAPSED_PROCESS}).
276 *
277 * @return an array of double. First number is the deviation itself and
278 * 	the second is the standard error.
279 */
280public double[] getCurrentBuildDeltaInfo() {
281	if (this.baseline == null || this.current == null) {
282		initialize();
283	}
284	return new double[] { this.delta, this.error };
285}
286
287/**
288 * Returns the error of the current build results
289 *
290 * @return the error made during the current build measure
291 */
292public double getCurrentBuildError() {
293	if (this.current == null) {
294	    initialize();
295    }
296	return this.current.getError();
297}
298
299/**
300 * Returns the current build name.
301 *
302 * @return The name of the current build
303 * @see #getCurrentBuildResults()
304 */
305public String getCurrentBuildName() {
306	if (this.current == null) {
307	    initialize();
308    }
309	return this.current.getName();
310}
311
312/**
313 * Returns the current build results.
314 * <p>
315 * This build is currently the last integration or nightly
316 * build which has performance results in the database.
317 * It may differ from the {@link PerformanceResults#getName()}.
318 *
319 * @return The current build results.
320 * @see BuildResults
321 */
322public BuildResults getCurrentBuildResults() {
323	if (this.current == null) {
324	    initialize();
325    }
326	return this.current;
327}
328
329/**
330 * Returns the current build result value.
331 *
332 * @return The value of the current build results.
333 */
334public double getCurrentBuildValue() {
335	if (this.current == null) {
336	    initialize();
337    }
338	return this.current.getValue();
339}
340
341/**
342 * Returns the delta between current and baseline builds results.
343 *
344 * @return the delta
345 */
346public double getDelta() {
347	if (this.baseline == null || this.current == null) {
348		initialize();
349	}
350	return this.delta;
351}
352
353/**
354 * Returns the standard error of the delta between current and baseline builds results.
355 *
356 * @return the delta
357 * @see #getDelta()
358 */
359public double getError() {
360	if (this.baseline == null || this.current == null) {
361		initialize();
362	}
363	return this.error;
364}
365
366/**
367 * Return the name of the machine associated with the current config.
368 *
369 * @return The name of the machine.
370 */
371public String getLabel() {
372	return this.parent.getPerformance().sortedConfigDescriptions[this.id];
373}
374
375/**
376 * Get all dimension builds default dimension statistics for all builds.
377 *
378 * @return An array of double built as follows:
379 * <ul>
380 * <li>0:	numbers of values</li>
381 * <li>1:	mean of values</li>
382 * <li>2:	standard deviation of these values</li>
383 * <li>3:	coefficient of variation of these values</li>
384 * </ul>
385 */
386public double[] getStatistics() {
387	return getStatistics(Util.ALL_BUILD_PREFIXES, DB_Results.getDefaultDimension().getId());
388}
389
390/**
391 * Get all dimension builds default dimension statistics for a given list of build
392 * prefixes.
393 *
394 * @param prefixes List of prefixes to filter builds. If <code>null</code>
395 * 	then all the builds are taken to compute statistics.
396 * @return An array of double built as follows:
397 * <ul>
398 * <li>0:	numbers of values</li>
399 * <li>1:	mean of values</li>
400 * <li>2:	standard deviation of these values</li>
401 * <li>3:	coefficient of variation of these values</li>
402 * </ul>
403 */
404public double[] getStatistics(List prefixes) {
405	return getStatistics(prefixes, DB_Results.getDefaultDimension().getId());
406}
407
408/**
409 * Get all dimension builds statistics for a given list of build prefixes
410 * and a given dimension.
411 *
412 * @param prefixes List of prefixes to filter builds. If <code>null</code>
413 * 	then all the builds are taken to compute statistics.
414 * @param dim_id The id of the dimension on which the statistics must be computed
415 * @return An array of double built as follows:
416 * <ul>
417 * <li>0:	numbers of values</li>
418 * <li>1:	mean of values</li>
419 * <li>2:	standard deviation of these values</li>
420 * <li>3:	coefficient of variation of these values</li>
421 * </ul>
422 */
423public double[] getStatistics(List prefixes, int dim_id) {
424	int size = size();
425	int length = prefixes == null ? 0 : prefixes.size();
426	int count = 0;
427	double mean=0, stddev=0, variation=0;
428	double[] values = new double[size];
429	count = 0;
430	mean = 0.0;
431	for (int i=0; i<size; i++) {
432		BuildResults buildResults = (BuildResults) this.children.get(i);
433		String buildName = buildResults.getName();
434		if (isBuildConcerned(buildResults)) {
435			if (prefixes == null) {
436				double value = buildResults.getValue(dim_id);
437				values[count] = value;
438				mean += value;
439				count++;
440			} else {
441				for (int j=0; j<length; j++) {
442					if (buildName.startsWith((String)prefixes.get(j))) {
443						double value = buildResults.getValue(dim_id);
444						values[count] = value;
445						mean += value;
446						count++;
447					}
448				}
449			}
450		}
451	}
452	mean /= count;
453	for (int i=0; i<count; i++) {
454		stddev += Math.pow(values[i] - mean, 2);
455	}
456	stddev = Math.sqrt((stddev / (count - 1)));
457	variation = stddev / mean;
458	return new double[] { count, mean, stddev, variation };
459}
460
461private void initialize() {
462	reset();
463	// Get performance results builds name
464	PerformanceResults perfResults = getPerformance();
465	String baselineBuildName = perfResults.getBaselineName();
466	String baselineBuildDate = baselineBuildName == null ? null : Util.getBuildDate(baselineBuildName);
467	String currentBuildName = perfResults.name;
468	String currentBuildDate = currentBuildName == null ? null : Util.getBuildDate(currentBuildName);
469
470	// Set baseline and current builds
471	BuildResults lastBaseline = null;
472	int size = size();
473	if (size == 0) return;
474	for (int i=0; i<size; i++) {
475		BuildResults buildResults = (BuildResults) this.children.get(i);
476		if (buildResults.values != null) {
477			buildResults.cleanValues();
478		}
479		if (buildResults.isBaseline()) {
480			if (lastBaseline == null || baselineBuildDate == null || baselineBuildDate.compareTo(buildResults.getDate()) >= 0) {
481				lastBaseline = buildResults;
482			}
483			if (baselineBuildName != null && buildResults.getName().equals(baselineBuildName)) {
484				this.baseline = buildResults;
485				this.baselined = true;
486			}
487		} else if (currentBuildName == null || currentBuildDate == null || (this.current == null && buildResults.getDate().compareTo(currentBuildDate) >= 0)) {
488			this.current = buildResults;
489			this.valid = true;
490		}
491	}
492	if (this.baseline == null) {
493		this.baseline = (lastBaseline == null) ? (BuildResults) this.children.get(0) : lastBaseline;
494	}
495	if (this.current == null) {
496		int idx = size() - 1;
497		BuildResults previous = (BuildResults) this.children.get(idx--);
498		while (idx >= 0 && previous.isBaseline()) {
499			previous = (BuildResults) this.children.get(idx--);
500		}
501		this.current = previous;
502	}
503
504	// Set delta between current vs. baseline and the corresponding error
505	int dim_id = DB_Results.getDefaultDimension().getId();
506	double baselineValue = this.baseline.getValue();
507	double currentValue = this.current.getValue();
508	this.delta = (currentValue - baselineValue) / baselineValue;
509	if (Double.isNaN(this.delta)) {
510		this.error = Double.NaN;
511	} else {
512		long baselineCount = this.baseline.getCount(dim_id);
513		long currentCount = this.current.getCount(dim_id);
514		if (baselineCount == 1 || currentCount == 1) {
515			this.error = Double.NaN;
516		} else {
517			double baselineError = this.baseline.getError(dim_id);
518			double currentError = this.current.getError(dim_id);
519			this.error = Double.isNaN(baselineError)
520					? currentError / baselineValue
521					: Math.sqrt(baselineError*baselineError + currentError*currentError) / baselineValue;
522		}
523	}
524
525	// Set the failure on the current build if necessary
526	int failure_threshold = getPerformance().failure_threshold;
527	if (this.delta >= (failure_threshold/100.0)) {
528		StringBuffer buffer = new StringBuffer("Performance criteria not met when compared to '"); //$NON-NLS-1$
529		buffer.append(this.baseline.getName());
530		buffer.append("': "); //$NON-NLS-1$
531		buffer.append(DB_Results.getDefaultDimension().getName());
532		buffer.append("= "); //$NON-NLS-1$
533		buffer.append(Util.timeString((long)this.current.getValue()));
534		buffer.append(" is not within [0%, "); //$NON-NLS-1$
535		buffer.append(100+failure_threshold);
536		buffer.append("'%] of "); //$NON-NLS-1$
537		buffer.append(Util.timeString((long)this.baseline.getValue()));
538		this.current.setFailure(buffer.toString());
539	}
540}
541
542/**
543 * Returns whether the configuration has results for the performance
544 * baseline build or not.
545 *
546 * @return <code>true</code> if the configuration has results
547 * 	for the performance baseline build, <code>false</code> otherwise.
548 */
549public boolean isBaselined() {
550	if (this.baseline == null || this.current == null) {
551	    initialize();
552    }
553	return this.baselined;
554}
555
556boolean isBuildConcerned(BuildResults buildResults) {
557	String buildDate = buildResults.getDate();
558	String currentBuildDate = getCurrentBuildResults() == null ? null : getCurrentBuildResults().getDate();
559	String baselineBuildDate = getBaselineBuildResults() == null ? null : getBaselineBuildResults().getDate();
560	return ((currentBuildDate == null || buildDate.compareTo(currentBuildDate) <= 0) &&
561		(baselineBuildDate == null || buildDate.compareTo(baselineBuildDate) <= 0));
562}
563/**
564 * Returns whether the configuration has results for the performance
565 * current build or not.
566 *
567 * @return <code>true</code> if the configuration has results
568 * 	for the performance current build, <code>false</code> otherwise.
569 */
570public boolean isValid() {
571	if (this.baseline == null || this.current == null) {
572	    initialize();
573    }
574	return this.valid;
575}
576
577/**
578 * Returns the 'n' last nightly build names.
579 *
580 * @param n Number of last nightly builds to return
581 * @return Last n nightly build names preceding current.
582 */
583public List lastNightlyBuildNames(int n) {
584	List labels = new ArrayList();
585	for (int i=size()-2; i>=0; i--) {
586		BuildResults buildResults = (BuildResults) this.children.get(i);
587		if (isBuildConcerned(buildResults)) {
588			String buildName = buildResults.getName();
589			if (buildName.startsWith("N")) { //$NON-NLS-1$
590				labels.add(buildName);
591				if (labels.size() >= n) {
592	                break;
593                }
594			}
595		}
596	}
597	return labels;
598}
599
600/*
601 * Read all configuration builds results data from the given stream.
602 */
603void readData(DataInputStream stream) throws IOException {
604	int size = stream.readInt();
605	for (int i=0; i<size; i++) {
606		BuildResults buildResults = new BuildResults(this);
607		buildResults.readData(stream);
608		String lastBuildName = getPerformance().lastBuildName;
609		if (lastBuildName == null || buildResults.getDate().compareTo(Util.getBuildDate(lastBuildName)) <= 0) {
610			addChild(buildResults, true);
611		}
612	}
613}
614
615private void reset() {
616	this.current = null;
617	this.baseline = null;
618	this.baselined = false;
619	this.valid = false;
620	this.delta = 0;
621	this.error = -1;
622}
623
624/*
625 * Set the configuration value from database information
626 */
627void setInfos(int build_id, int summaryKind, String comment) {
628	BuildResults buildResults = (BuildResults) getResults(build_id);
629	if (buildResults == null) {
630		buildResults = new BuildResults(this, build_id);
631		addChild(buildResults, true);
632	}
633	buildResults.summaryKind = summaryKind;
634	buildResults.comment = comment;
635}
636
637/*
638 * Set the configuration value from database information
639 */
640void setValue(int build_id, int dim_id, int step, long value) {
641//	reset();
642	BuildResults buildResults = (BuildResults) getResults(build_id);
643	if (buildResults == null) {
644		buildResults = new BuildResults(this, build_id);
645		addChild(buildResults, true);
646	}
647	buildResults.setValue(dim_id, step, value);
648}
649
650/*
651 * Write all configuration builds results data into the given stream.
652 */
653void write(DataOutputStream stream) throws IOException {
654	int size = size();
655	stream.writeInt(this.id);
656	stream.writeInt(size);
657	for (int i=0; i<size; i++) {
658		BuildResults buildResults = (BuildResults) this.children.get(i);
659		buildResults.write(stream);
660	}
661}
662
663}
664