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.util.ArrayList; 17 import java.util.List; 18 import org.eclipse.test.internal.performance.InternalDimensions; 19 import 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 */ 29 public class ConfigResults extends AbstractResults { 30 BuildResults baseline, current; 31 boolean baselined = false, valid = false; 32 double delta, error; 33 34 public 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 */ 43 void 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 */ 55 public 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 */ 68 public 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 */ 82 public 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 */ 103 public 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 */ 115 public 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 */ 126 public 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 */ 137 public 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 */ 155 public 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 */ 174 public 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 */ 208 public 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 */ 242 double[] 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 */ 280 public 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 */ 292 public 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 */ 305 public 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 */ 322 public 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 */ 334 public 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 */ 346 public 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 */ 359 public 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 */ 371 public 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 */ 386 public 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 */ 404 public 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 */ 423 public 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 461 private 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 */ 549 public boolean isBaselined() { 550 if (this.baseline == null || this.current == null) { 551 initialize(); 552 } 553 return this.baselined; 554 } 555 556 boolean 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 */ 570 public 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 */ 583 public 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 */ 603 void 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 615 private 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 */ 627 void 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 */ 640 void 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 */ 653 void 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