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.text.ParseException;
17import java.util.StringTokenizer;
18
19import org.eclipse.test.internal.performance.InternalPerformanceMeter;
20import org.eclipse.test.internal.performance.PerformanceTestPlugin;
21import org.eclipse.test.internal.performance.data.Dim;
22import org.eclipse.test.internal.performance.results.utils.Util;
23
24/**
25 * Class providing numbers of a scenario running on a specific configuration
26 * at a specific time (for example 'I20070615-1200').
27 */
28public class BuildResults extends AbstractResults {
29
30	private static final double IMPOSSIBLE_VALUE = -1E6;
31
32	// Build information
33	String date;
34	String comment;
35	int summaryKind = -1;
36
37	// Dimensions information
38	Dim[] dimensions;
39	double[] average, stddev;
40	long[] count;
41	double[][] values;
42	boolean hadValues = false;
43	private int defaultDimIndex = -1;
44
45	// Comparison information
46	boolean baseline;
47	String failure;
48
49BuildResults(AbstractResults parent) {
50	super(parent, -1);
51}
52
53BuildResults(AbstractResults parent, int id) {
54	super(parent, id);
55	this.name = DB_Results.getBuildName(id);
56	this.baseline = this.name.startsWith(DB_Results.getDbBaselinePrefix());
57}
58
59/*
60 * Clean values when several measures has been done for the same build.
61 */
62void cleanValues() {
63	int length = this.values.length;
64	for (int dim_id=0; dim_id<length; dim_id++) {
65		int vLength = this.values[dim_id].length;
66		/* Log clean operation
67		if (dim_id == 0) {
68			IStatus status = new Status(IStatus.WARNING, PerformanceTestPlugin.PLUGIN_ID, "Clean "+vLength+" values for "+this.parent+">"+this.name+" ("+this.count[dim_id]+" measures)...");    //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ //$NON-NLS-5$
69			PerformanceTestPlugin.log(status);
70		}
71		*/
72		this.average[dim_id] = 0;
73		for (int i=0; i<vLength; i++) {
74			this.average[dim_id] += this.values[dim_id][i];
75		}
76		this.average[dim_id] /= vLength;
77		double squaredDeviations= 0;
78		for (int i=0; i<vLength; i++) {
79		    double deviation= this.average[dim_id] - this.values[dim_id][i];
80		    squaredDeviations += deviation * deviation;
81		}
82		this.stddev[dim_id] = Math.sqrt(squaredDeviations / (this.count[dim_id] - 1)); // unbiased sample stdev
83		this.values[dim_id] = null;
84	}
85	for (int i=0; i<length; i++) {
86		if (this.values[i] != null) {
87			return;
88		}
89	}
90	this.values = null;
91	this.hadValues = true;
92}
93
94/**
95 * Compare build results using the date of the build.
96 *
97 * @see Comparable#compareTo(Object)
98 */
99public int compareTo(Object obj) {
100	if (obj instanceof BuildResults) {
101		BuildResults res = (BuildResults) obj;
102		return getDate().compareTo(res.getDate());
103	}
104	return -1;
105}
106
107/**
108 * Returns the most recent baseline build results.
109 *
110 * @return The {@link BuildResults baseline build results}.
111 * @see BuildResults
112 */
113public BuildResults getBaselineBuildResults() {
114	return ((ConfigResults)this.parent).getBaselineBuildResults();
115}
116
117/**
118 * Returns the comment associated with the scenario for the current build.
119 *
120 * @return The comment associated with the scenario for the current build
121 * 	or <code>null</code> if no comment was stored for it.
122 */
123public String getComment() {
124	return this.comment;
125}
126
127/**
128 * Return the number of stored values for the default dimension
129 *
130 * @return the number of stored values for the default dimension
131 */
132public long getCount() {
133	if (this.defaultDimIndex < 0) {
134		this.defaultDimIndex = DB_Results.getDefaultDimensionIndex();
135	}
136	return this.count[this.defaultDimIndex];
137}
138
139/**
140 * Return the number of stored values for the given dimension.
141 *
142 * @param dim_id The id of the dimension (see {@link Dim#getId()})
143 * @return the number of stored values for the given dimension
144 *
145 */
146public long getCount(int dim_id) {
147	return this.count[getDimIndex(dim_id)];
148}
149
150/**
151 * Returns the date of the build which is a part of its name.
152 *
153 * @return The date of the build as yyyyMMddHHmm
154 */
155public String getDate() {
156	if (this.date == null) {
157		if (this.baseline) {
158			int length = this.name.length();
159			this.date = this.name.substring(length-12, length);
160		} else {
161			char first = this.name.charAt(0);
162			if (first == 'N' || first == 'I' || first == 'M') { // TODO (frederic) should be buildIdPrefixes...
163				if (this.name.length() == 14) {
164					this.date = this.name.substring(1, 9)+this.name.substring(10, 14);
165				} else {
166					this.date = this.name.substring(1);
167				}
168			} else {
169				int length = this.name.length() - 12 /* length of date */;
170				for (int i=0; i<=length; i++) {
171					try {
172						String substring = i == 0 ? this.name : this.name.substring(i);
173						Util.DATE_FORMAT.parse(substring);
174						this.date = substring; // if no exception is raised then the substring has a correct date format => store it
175						break;
176					} catch(ParseException ex) {
177						// skip
178					}
179				}
180			}
181		}
182	}
183	return this.date;
184}
185
186/**
187 * Returns the standard deviation of the default dimension computed
188 * while running the scenario for the current build.
189 *
190 * @return The value of the standard deviation
191 */
192public double getDeviation() {
193	if (this.defaultDimIndex < 0) {
194		this.defaultDimIndex = DB_Results.getDefaultDimensionIndex();
195	}
196	return this.stddev[this.defaultDimIndex];
197}
198
199/**
200 * Returns the standard deviation of the given dimension computed
201 * while running the scenario for the current build.
202 *
203 * @param dim_id The id of the dimension (see {@link Dim#getId()})
204 * @return The value of the standard deviation
205 */
206public double getDeviation(int dim_id) {
207	final int dimIndex = getDimIndex(dim_id);
208	return dimIndex < 0 ? 0 : this.stddev[dimIndex];
209}
210
211/**
212 * Returns the dimensions supported for the current build.
213 *
214 * @return An array of dimensions.
215 */
216public Dim[] getDimensions() {
217	return this.dimensions;
218}
219
220/**
221 * Returns the kind of summary for the scenario of the current build.
222 *
223 * @return -1 if the scenario has no summary, 1 if it's a global summary, 0 otherwise.
224 */
225public int getSummaryKind() {
226	return this.summaryKind;
227}
228
229/**
230 * Returns whether the current build had several values stored in database.
231 *
232 * @return <code>true</code> if the measure was committed several times,
233 * <code>false</code> otherwise.
234 */
235public boolean hadValues() {
236	return this.hadValues;
237}
238
239/*
240 * Return the index of the dimension corresponding to the given
241 * dimension id (see {@link Dim#getId()})
242 */
243int getDimIndex(int dim_id) {
244	if (this.dimensions == null) return -1;
245	int length = this.dimensions.length;
246	for (int i=0; i<length; i++) {
247		if (this.dimensions[i] == null) break;
248		if (this.dimensions[i].getId() == dim_id) {
249			return i;
250		}
251	}
252	return -1;
253}
254
255/**
256 * Return the error computed while storing values for the default dimension
257 *
258 * @return the error of the measures stored for the default dimension
259 */
260public double getError() {
261	long n = getCount();
262	if (n == 1) return Double.NaN;
263	return getDeviation() / Math.sqrt(n);
264}
265
266/**
267 * Return the error computed while storing values for the given dimension.
268 *
269 * @param dim_id The id of the dimension (see {@link Dim#getId()})
270 * @return the error of the measures stored for the given dimension
271 */
272public double getError(int dim_id) {
273	long n = getCount(dim_id);
274	if (n == 1) return Double.NaN;
275	return getDeviation(dim_id) / Math.sqrt(n);
276}
277
278/**
279 * Return the failure message which may happened on this scenario
280 * while running the current build
281 *
282 * @return The failure message or <code>null</null> if the scenario passed.
283 */
284public String getFailure() {
285	return this.failure;
286}
287
288/**
289 * Return the value of the performance result stored
290 * for the given dimension of the current build.
291 *
292 * @param dim_id The id of the dimension (see {@link Dim#getId()})
293 * @return The value of the performance result
294 */
295public double getValue(int dim_id) {
296	int idx = getDimIndex(dim_id);
297	if (idx < 0) return Double.NaN;
298	return this.average[idx];
299}
300
301/**
302 * Return the value of the performance result stored
303 * for the default dimension of the current build.
304 *
305 * @return The value of the performance result
306 */
307public double getValue() {
308	if (this.defaultDimIndex < 0) {
309		this.defaultDimIndex = DB_Results.getDefaultDimensionIndex();
310	}
311	return this.average[this.defaultDimIndex];
312}
313
314/**
315 * Returns whether the build is a baseline build or not.
316 *
317 * @return <code>true</code> if the build name starts with the baseline prefix
318 * 	(see {@link PerformanceResults#getBaselinePrefix()} or <code>false</code>
319 * 	otherwise.
320 */
321public boolean isBaseline() {
322	return this.baseline;
323}
324
325/**
326 * Returns whether the build has a summary or not. Note that the summary
327 * may be global or not.
328 *
329 * @return <code>true</code> if the summary kind is equals to 0 or 1
330 * 	<code>false</code> otherwise.
331 */
332public boolean hasSummary() {
333	return this.summaryKind >= 0;
334}
335/**
336 * Returns whether the build has a global summary or not.
337 *
338 * @return <code>true</code> if the summary kind is equals to 1
339 * 	<code>false</code> otherwise.
340 */
341public boolean hasGlobalSummary() {
342	return this.summaryKind == 1;
343}
344
345/*
346 * Returns a given pattern match the build name or not.
347 */
348boolean match(String pattern) {
349	if (pattern.equals("*")) return true; //$NON-NLS-1$
350	if (pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0) {
351		pattern += "*"; //$NON-NLS-1$
352	}
353	StringTokenizer tokenizer = new StringTokenizer(pattern, "*?", true); //$NON-NLS-1$
354	int start = 0;
355	String previous = ""; //$NON-NLS-1$
356	while (tokenizer.hasMoreTokens()) {
357		String token = tokenizer.nextToken();
358		if (!token.equals("*") && !token.equals("?")) { //$NON-NLS-1$ //$NON-NLS-2$
359			if (previous.equals("*")) { //$NON-NLS-1$
360				int idx = this.name.substring(start).indexOf(token);
361				if (idx < 0) return false;
362				start += idx;
363			} else {
364				if (previous.equals("?")) start++; //$NON-NLS-1$
365				if (!this.name.substring(start).startsWith(token)) return false;
366			}
367			start += token.length();
368		}
369		previous = token;
370	}
371	if (previous.equals("*")) { //$NON-NLS-1$
372		return true;
373	} else if (previous.equals("?")) { //$NON-NLS-1$
374		return this.name.length() == start;
375	}
376	return this.name.endsWith(previous);
377}
378
379/*
380 * Read the build results data from the given stream.
381 */
382void readData(DataInputStream stream) throws IOException {
383	long timeBuild = stream.readLong();
384	this.date = new Long(timeBuild).toString();
385	byte kind = stream.readByte();
386	this.baseline = kind == 0;
387	if (this.baseline) {
388		this.name = getPerformance().baselinePrefix + '_' + this.date;
389	} else {
390		String suffix = this.date.substring(0, 8) + '-' + this.date.substring(8);
391		switch (kind) {
392			case 1:
393				this.name = "N" + suffix; //$NON-NLS-1$
394				break;
395			case 2:
396				this.name = "I" + suffix; //$NON-NLS-1$
397				break;
398			case 3:
399				this.name = "M" + suffix; //$NON-NLS-1$
400				break;
401			default:
402				this.name = stream.readUTF();
403				break;
404		}
405	}
406	int length = stream.readInt();
407	this.dimensions = new Dim[length];
408	this.average = new double[length];
409	this.stddev = new double[length];
410	this.count = new long[length];
411	for (int i=0; i<length; i++) {
412		int dimId = stream.readInt();
413		DB_Results.storeDimension(dimId);
414		this.dimensions[i] = (Dim) PerformanceTestPlugin.getDimension(dimId);
415		this.average[i] = stream.readLong();
416		this.count[i] = stream.readLong();
417		this.stddev[i] = stream.readDouble();
418	}
419	this.id = DB_Results.getBuildId(this.name);
420
421	// read summary
422	this.summaryKind = stream.readInt();
423
424	// read comment
425	String str = stream.readUTF();
426	if (str.length() > 0) {
427		this.comment = str;
428	}
429}
430
431/*
432 * Set the build summary and its associated comment.
433 */
434void setComment(String comment) {
435	if (comment != null && this.comment == null) {
436		this.comment = comment;
437	}
438}
439
440/*
441 * Set the build summary and its associated comment.
442 */
443void setSummary(int kind, String comment) {
444	this.comment = comment;
445	this.summaryKind = kind;
446}
447
448/*
449 * Set the build failure.
450 */
451void setFailure(String failure) {
452	this.failure = failure;
453}
454
455/*
456 * Set the build value from database information.
457 */
458void setValue(int dim_id, int step, long value) {
459	int length = DB_Results.getDimensions().length;
460	Dim dimension = (Dim) PerformanceTestPlugin.getDimension(dim_id);
461	int idx = 0;
462	if (this.dimensions == null){
463		this.dimensions = new Dim[length];
464		this.average = new double[length];
465		this.stddev = new double[length];
466		this.count = new long[length];
467		this.dimensions[0] = dimension;
468		for (int i=0; i<length; i++) {
469			// init average numbers with an impossible value
470			// to clearly identify whether it's already set or not
471			// when several measures are made for the same build
472			this.average[i] = IMPOSSIBLE_VALUE;
473		}
474	} else {
475		length = this.dimensions.length;
476		for (int i=0; i<length; i++) {
477			if (this.dimensions[i] == null) {
478				this.dimensions[i] = dimension;
479				idx = i;
480				break;
481			}
482			if (this.dimensions[i].getId() == dim_id) {
483				idx = i;
484				break;
485			}
486		}
487	}
488	switch (step) {
489		case InternalPerformanceMeter.AVERAGE:
490			if (this.average[idx] != IMPOSSIBLE_VALUE) {
491				if (this.values == null) {
492					this.values = new double[length][];
493					this.values[idx] = new double[2];
494					this.values[idx][0] = this.average[idx];
495					this.values[idx][1] = value;
496					this.average[idx] = IMPOSSIBLE_VALUE;
497				} else if (this.values[idx] == null) {
498					this.values[idx] = new double[2];
499					this.values[idx][0] = this.average[idx];
500					this.values[idx][1] = value;
501					this.average[idx] = IMPOSSIBLE_VALUE;
502				}
503			} else if (this.values != null && this.values[idx] != null) {
504				int vLength = this.values[idx].length;
505				System.arraycopy(this.values[idx], 0, this.values[idx] = new double[vLength+1], 0, vLength);
506				this.values[idx][vLength] = value;
507			} else {
508				this.average[idx] = value;
509			}
510			break;
511		case InternalPerformanceMeter.STDEV:
512			this.stddev[idx] += Double.longBitsToDouble(value);
513			break;
514		case InternalPerformanceMeter.SIZE:
515			this.count[idx] += value;
516			break;
517	}
518}
519
520/* (non-Javadoc)
521 * @see org.eclipse.test.internal.performance.results.AbstractResults#toString()
522 */
523public String toString() {
524	StringBuffer buffer = new StringBuffer(this.name);
525	buffer.append(": "); //$NON-NLS-1$
526	int length = this.dimensions.length;
527	for (int i=0; i<length; i++) {
528		if (i>0)	buffer.append(", "); //$NON-NLS-1$
529		buffer.append('[');
530		buffer.append(this.dimensions[i].getId());
531		buffer.append("]="); //$NON-NLS-1$
532		buffer.append(this.average[i]);
533		buffer.append('/');
534		buffer.append(this.count[i]);
535		buffer.append('/');
536		buffer.append(Math.round(this.stddev[i]*1000)/1000.0);
537	}
538	return buffer.toString();
539}
540
541/*
542 * Write the build results data in the given stream.
543 */
544void write(DataOutputStream stream) throws IOException {
545	long timeBuild = -1;
546    try {
547	    timeBuild = Long.parseLong(getDate());
548    } catch (NumberFormatException nfe) {
549	    // do nothing
550    	nfe.printStackTrace();
551    }
552	stream.writeLong(timeBuild);
553	byte kind = 0; // baseline
554	if (!this.baseline) {
555		switch (this.name.charAt(0)) {
556			case 'N':
557				kind = 1;
558				break;
559			case 'I':
560				kind = 2;
561				break;
562			case 'M':
563				kind = 3;
564				break;
565			default:
566				kind = 4;
567				break;
568		}
569	}
570	stream.writeByte(kind);
571	if (kind == 4) {
572		stream.writeUTF(this.name);
573	}
574	int length = this.dimensions == null ? 0 : this.dimensions.length;
575	stream.writeInt(length);
576	for (int i=0; i<length; i++) {
577		stream.writeInt(this.dimensions[i].getId());
578		stream.writeLong((long)this.average[i]) ;
579		stream.writeLong(this.count[i]);
580		stream.writeDouble(this.stddev[i]);
581	}
582
583	// Write extra infos (summary, failure and comment)
584	stream.writeInt(this.summaryKind);
585	stream.writeUTF(this.comment == null ? "" : this.comment) ; //$NON-NLS-1$
586}
587
588}
589