/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.test.internal.performance.results.db; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.text.ParseException; import java.util.StringTokenizer; import org.eclipse.test.internal.performance.InternalPerformanceMeter; import org.eclipse.test.internal.performance.PerformanceTestPlugin; import org.eclipse.test.internal.performance.data.Dim; import org.eclipse.test.internal.performance.results.utils.Util; /** * Class providing numbers of a scenario running on a specific configuration * at a specific time (for example 'I20070615-1200'). */ public class BuildResults extends AbstractResults { private static final double IMPOSSIBLE_VALUE = -1E6; // Build information String date; String comment; int summaryKind = -1; // Dimensions information Dim[] dimensions; double[] average, stddev; long[] count; double[][] values; boolean hadValues = false; private int defaultDimIndex = -1; // Comparison information boolean baseline; String failure; BuildResults(AbstractResults parent) { super(parent, -1); } BuildResults(AbstractResults parent, int id) { super(parent, id); this.name = DB_Results.getBuildName(id); this.baseline = this.name.startsWith(DB_Results.getDbBaselinePrefix()); } /* * Clean values when several measures has been done for the same build. */ void cleanValues() { int length = this.values.length; for (int dim_id=0; dim_id<length; dim_id++) { int vLength = this.values[dim_id].length; /* Log clean operation if (dim_id == 0) { 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$ PerformanceTestPlugin.log(status); } */ this.average[dim_id] = 0; for (int i=0; i<vLength; i++) { this.average[dim_id] += this.values[dim_id][i]; } this.average[dim_id] /= vLength; double squaredDeviations= 0; for (int i=0; i<vLength; i++) { double deviation= this.average[dim_id] - this.values[dim_id][i]; squaredDeviations += deviation * deviation; } this.stddev[dim_id] = Math.sqrt(squaredDeviations / (this.count[dim_id] - 1)); // unbiased sample stdev this.values[dim_id] = null; } for (int i=0; i<length; i++) { if (this.values[i] != null) { return; } } this.values = null; this.hadValues = true; } /** * Compare build results using the date of the build. * * @see Comparable#compareTo(Object) */ public int compareTo(Object obj) { if (obj instanceof BuildResults) { BuildResults res = (BuildResults) obj; return getDate().compareTo(res.getDate()); } return -1; } /** * Returns the most recent baseline build results. * * @return The {@link BuildResults baseline build results}. * @see BuildResults */ public BuildResults getBaselineBuildResults() { return ((ConfigResults)this.parent).getBaselineBuildResults(); } /** * Returns the comment associated with the scenario for the current build. * * @return The comment associated with the scenario for the current build * or <code>null</code> if no comment was stored for it. */ public String getComment() { return this.comment; } /** * Return the number of stored values for the default dimension * * @return the number of stored values for the default dimension */ public long getCount() { if (this.defaultDimIndex < 0) { this.defaultDimIndex = DB_Results.getDefaultDimensionIndex(); } return this.count[this.defaultDimIndex]; } /** * Return the number of stored values for the given dimension. * * @param dim_id The id of the dimension (see {@link Dim#getId()}) * @return the number of stored values for the given dimension * */ public long getCount(int dim_id) { return this.count[getDimIndex(dim_id)]; } /** * Returns the date of the build which is a part of its name. * * @return The date of the build as yyyyMMddHHmm */ public String getDate() { if (this.date == null) { if (this.baseline) { int length = this.name.length(); this.date = this.name.substring(length-12, length); } else { char first = this.name.charAt(0); if (first == 'N' || first == 'I' || first == 'M') { // TODO (frederic) should be buildIdPrefixes... if (this.name.length() == 14) { this.date = this.name.substring(1, 9)+this.name.substring(10, 14); } else { this.date = this.name.substring(1); } } else { int length = this.name.length() - 12 /* length of date */; for (int i=0; i<=length; i++) { try { String substring = i == 0 ? this.name : this.name.substring(i); Util.DATE_FORMAT.parse(substring); this.date = substring; // if no exception is raised then the substring has a correct date format => store it break; } catch(ParseException ex) { // skip } } } } } return this.date; } /** * Returns the standard deviation of the default dimension computed * while running the scenario for the current build. * * @return The value of the standard deviation */ public double getDeviation() { if (this.defaultDimIndex < 0) { this.defaultDimIndex = DB_Results.getDefaultDimensionIndex(); } return this.stddev[this.defaultDimIndex]; } /** * Returns the standard deviation of the given dimension computed * while running the scenario for the current build. * * @param dim_id The id of the dimension (see {@link Dim#getId()}) * @return The value of the standard deviation */ public double getDeviation(int dim_id) { int dimIndex = getDimIndex(dim_id); if (dimIndex == -1) { return -1; } return this.stddev[dimIndex]; } /** * Returns the dimensions supported for the current build. * * @return An array of dimensions. */ public Dim[] getDimensions() { return this.dimensions; } /** * Returns the kind of summary for the scenario of the current build. * * @return -1 if the scenario has no summary, 1 if it's a global summary, 0 otherwise. */ public int getSummaryKind() { return this.summaryKind; } /** * Returns whether the current build had several values stored in database. * * @return <code>true</code> if the measure was committed several times, * <code>false</code> otherwise. */ public boolean hadValues() { return this.hadValues; } /* * Return the index of the dimension corresponding to the given * dimension id (see {@link Dim#getId()}) */ int getDimIndex(int dim_id) { if (this.dimensions == null) return -1; int length = this.dimensions.length; for (int i=0; i<length; i++) { if (this.dimensions[i] == null) break; if (this.dimensions[i].getId() == dim_id) { return i; } } return -1; } /** * Return the error computed while storing values for the default dimension * * @return the error of the measures stored for the default dimension */ public double getError() { long n = getCount(); if (n == 1) return Double.NaN; return getDeviation() / Math.sqrt(n); } /** * Return the error computed while storing values for the given dimension. * * @param dim_id The id of the dimension (see {@link Dim#getId()}) * @return the error of the measures stored for the given dimension */ public double getError(int dim_id) { long n = getCount(dim_id); if (n == 1) return Double.NaN; return getDeviation(dim_id) / Math.sqrt(n); } /** * Return the failure message which may happened on this scenario * while running the current build * * @return The failure message or <code>null</null> if the scenario passed. */ public String getFailure() { return this.failure; } /** * Return the value of the performance result stored * for the given dimension of the current build. * * @param dim_id The id of the dimension (see {@link Dim#getId()}) * @return The value of the performance result */ public double getValue(int dim_id) { int idx = getDimIndex(dim_id); if (idx < 0) return Double.NaN; return this.average[idx]; } /** * Return the value of the performance result stored * for the default dimension of the current build. * * @return The value of the performance result */ public double getValue() { if (this.defaultDimIndex < 0) { this.defaultDimIndex = DB_Results.getDefaultDimensionIndex(); } return this.average[this.defaultDimIndex]; } /** * Returns whether the build is a baseline build or not. * * @return <code>true</code> if the build name starts with the baseline prefix * (see {@link PerformanceResults#getBaselinePrefix()} or <code>false</code> * otherwise. */ public boolean isBaseline() { return this.baseline; } /** * Returns whether the build has a summary or not. Note that the summary * may be global or not. * * @return <code>true</code> if the summary kind is equals to 0 or 1 * <code>false</code> otherwise. */ public boolean hasSummary() { return this.summaryKind >= 0; } /** * Returns whether the build has a global summary or not. * * @return <code>true</code> if the summary kind is equals to 1 * <code>false</code> otherwise. */ public boolean hasGlobalSummary() { return this.summaryKind == 1; } /* * Returns a given pattern match the build name or not. */ boolean match(String pattern) { if (pattern.equals("*")) return true; //$NON-NLS-1$ if (pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0) { pattern += "*"; //$NON-NLS-1$ } StringTokenizer tokenizer = new StringTokenizer(pattern, "*?", true); //$NON-NLS-1$ int start = 0; String previous = ""; //$NON-NLS-1$ while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (!token.equals("*") && !token.equals("?")) { //$NON-NLS-1$ //$NON-NLS-2$ if (previous.equals("*")) { //$NON-NLS-1$ int idx = this.name.substring(start).indexOf(token); if (idx < 0) return false; start += idx; } else { if (previous.equals("?")) start++; //$NON-NLS-1$ if (!this.name.substring(start).startsWith(token)) return false; } start += token.length(); } previous = token; } if (previous.equals("*")) { //$NON-NLS-1$ return true; } else if (previous.equals("?")) { //$NON-NLS-1$ return this.name.length() == start; } return this.name.endsWith(previous); } /* * Read the build results data from the given stream. */ void readData(DataInputStream stream) throws IOException { long timeBuild = stream.readLong(); this.date = new Long(timeBuild).toString(); byte kind = stream.readByte(); this.baseline = kind == 0; if (this.baseline) { this.name = getPerformance().baselinePrefix + '_' + this.date; } else { String suffix = this.date.substring(0, 8) + '-' + this.date.substring(8); switch (kind) { case 1: this.name = "N" + suffix; //$NON-NLS-1$ break; case 2: this.name = "I" + suffix; //$NON-NLS-1$ break; case 3: this.name = "M" + suffix; //$NON-NLS-1$ break; default: this.name = stream.readUTF(); break; } } int length = stream.readInt(); this.dimensions = new Dim[length]; this.average = new double[length]; this.stddev = new double[length]; this.count = new long[length]; for (int i=0; i<length; i++) { int dimId = stream.readInt(); DB_Results.storeDimension(dimId); this.dimensions[i] = (Dim) PerformanceTestPlugin.getDimension(dimId); this.average[i] = stream.readLong(); this.count[i] = stream.readLong(); this.stddev[i] = stream.readDouble(); } this.id = DB_Results.getBuildId(this.name); // read summary this.summaryKind = stream.readInt(); // read comment String str = stream.readUTF(); if (str.length() > 0) { this.comment = str; } } /* * Set the build summary and its associated comment. */ void setComment(String comment) { if (comment != null && this.comment == null) { this.comment = comment; } } /* * Set the build summary and its associated comment. */ void setSummary(int kind, String comment) { this.comment = comment; this.summaryKind = kind; } /* * Set the build failure. */ void setFailure(String failure) { this.failure = failure; } /* * Set the build value from database information. */ void setValue(int dim_id, int step, long value) { int length = DB_Results.getDimensions().length; Dim dimension = (Dim) PerformanceTestPlugin.getDimension(dim_id); int idx = 0; if (this.dimensions == null){ this.dimensions = new Dim[length]; this.average = new double[length]; this.stddev = new double[length]; this.count = new long[length]; this.dimensions[0] = dimension; for (int i=0; i<length; i++) { // init average numbers with an impossible value // to clearly identify whether it's already set or not // when several measures are made for the same build this.average[i] = IMPOSSIBLE_VALUE; } } else { length = this.dimensions.length; for (int i=0; i<length; i++) { if (this.dimensions[i] == null) { this.dimensions[i] = dimension; idx = i; break; } if (this.dimensions[i].getId() == dim_id) { idx = i; break; } } } switch (step) { case InternalPerformanceMeter.AVERAGE: if (this.average[idx] != IMPOSSIBLE_VALUE) { if (this.values == null) { this.values = new double[length][]; this.values[idx] = new double[2]; this.values[idx][0] = this.average[idx]; this.values[idx][1] = value; this.average[idx] = IMPOSSIBLE_VALUE; } else if (this.values[idx] == null) { this.values[idx] = new double[2]; this.values[idx][0] = this.average[idx]; this.values[idx][1] = value; this.average[idx] = IMPOSSIBLE_VALUE; } } else if (this.values != null && this.values[idx] != null) { int vLength = this.values[idx].length; System.arraycopy(this.values[idx], 0, this.values[idx] = new double[vLength+1], 0, vLength); this.values[idx][vLength] = value; } else { this.average[idx] = value; } break; case InternalPerformanceMeter.STDEV: this.stddev[idx] += Double.longBitsToDouble(value); break; case InternalPerformanceMeter.SIZE: this.count[idx] += value; break; } } /* (non-Javadoc) * @see org.eclipse.test.internal.performance.results.AbstractResults#toString() */ public String toString() { StringBuffer buffer = new StringBuffer(this.name); buffer.append(": "); //$NON-NLS-1$ int length = this.dimensions.length; for (int i=0; i<length; i++) { if (i>0) buffer.append(", "); //$NON-NLS-1$ buffer.append('['); buffer.append(this.dimensions[i].getId()); buffer.append("]="); //$NON-NLS-1$ buffer.append(this.average[i]); buffer.append('/'); buffer.append(this.count[i]); buffer.append('/'); buffer.append(Math.round(this.stddev[i]*1000)/1000.0); } return buffer.toString(); } /* * Write the build results data in the given stream. */ void write(DataOutputStream stream) throws IOException { long timeBuild = -1; try { timeBuild = Long.parseLong(getDate()); } catch (NumberFormatException nfe) { // do nothing nfe.printStackTrace(); } stream.writeLong(timeBuild); byte kind = 0; // baseline if (!this.baseline) { switch (this.name.charAt(0)) { case 'N': kind = 1; break; case 'I': kind = 2; break; case 'M': kind = 3; break; default: kind = 4; break; } } stream.writeByte(kind); if (kind == 4) { stream.writeUTF(this.name); } int length = this.dimensions == null ? 0 : this.dimensions.length; stream.writeInt(length); for (int i=0; i<length; i++) { stream.writeInt(this.dimensions[i].getId()); stream.writeLong((long)this.average[i]) ; stream.writeLong(this.count[i]); stream.writeDouble(this.stddev[i]); } // Write extra infos (summary, failure and comment) stream.writeInt(this.summaryKind); stream.writeUTF(this.comment == null ? "" : this.comment) ; //$NON-NLS-1$ } }