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