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 *******************************************************************************/
11 package org.eclipse.test.internal.performance.results.db;
12
13 import java.io.DataInputStream;
14 import java.io.DataOutputStream;
15 import java.io.IOException;
16 import java.text.ParseException;
17 import java.util.StringTokenizer;
18
19 import org.eclipse.test.internal.performance.InternalPerformanceMeter;
20 import org.eclipse.test.internal.performance.PerformanceTestPlugin;
21 import org.eclipse.test.internal.performance.data.Dim;
22 import 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 */
28 public 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
BuildResults(AbstractResults parent)49 BuildResults(AbstractResults parent) {
50 super(parent, -1);
51 }
52
BuildResults(AbstractResults parent, int id)53 BuildResults(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 */
cleanValues()62 void 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 */
compareTo(Object obj)99 public 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 */
getBaselineBuildResults()113 public 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 */
getComment()123 public 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 */
getCount()132 public 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 */
getCount(int dim_id)146 public 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 */
getDate()155 public 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 */
getDeviation()192 public 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 */
getDeviation(int dim_id)206 public 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 */
getDimensions()216 public 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 */
getSummaryKind()225 public 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 */
hadValues()235 public 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 */
getDimIndex(int dim_id)243 int 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 */
getError()260 public 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 */
getError(int dim_id)272 public 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 */
getFailure()284 public 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 */
getValue(int dim_id)295 public 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 */
getValue()307 public 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 */
isBaseline()321 public 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 */
hasSummary()332 public 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 */
hasGlobalSummary()341 public boolean hasGlobalSummary() {
342 return this.summaryKind == 1;
343 }
344
345 /*
346 * Returns a given pattern match the build name or not.
347 */
match(String pattern)348 boolean 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 */
readData(DataInputStream stream)382 void 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 */
setComment(String comment)434 void 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 */
setSummary(int kind, String comment)443 void setSummary(int kind, String comment) {
444 this.comment = comment;
445 this.summaryKind = kind;
446 }
447
448 /*
449 * Set the build failure.
450 */
setFailure(String failure)451 void setFailure(String failure) {
452 this.failure = failure;
453 }
454
455 /*
456 * Set the build value from database information.
457 */
setValue(int dim_id, int step, long value)458 void 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 */
toString()523 public 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 */
write(DataOutputStream stream)544 void 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