/******************************************************************************* * 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.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.*; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.test.internal.performance.results.utils.Util; /** * Root class to handle performance results. * * Usually performance results are built for a current build vs. a baseline build. * * This class allow to read all data from releng performance database for given * configurations and scenario pattern. * * Then it provides easy and speedy access to all stored results. */ public class PerformanceResults extends AbstractResults { String[] allBuildNames = null; Map allScenarios; String lastBuildName; // Name of the last used build String baselineName; // Name of the baseline build used for comparison String baselinePrefix; private String scenarioPattern = "%"; //$NON-NLS-1$ private String[] components; String[] configNames, sortedConfigNames; String[] configDescriptions, sortedConfigDescriptions; private String configPattern; boolean dbRequired; boolean needToUpdateLocalFile; /* * Local class helping to guess remaining time while reading results from DB */ class RemainingTimeGuess { int all, count; long start; double remaining; RemainingTimeGuess(int all, long start) { this.all = all; this.start = start; } String display() { StringBuffer buffer = new StringBuffer(" [elapsed: "); //$NON-NLS-1$ long elapsed = getElapsed(); buffer.append(Util.timeChrono(elapsed)); if (this.count > 0) { buffer.append(" | left: "); //$NON-NLS-1$ long remainingTime = getRemainingTime(elapsed); buffer.append(Util.timeChrono(remainingTime)); buffer.append(" | end: "); //$NON-NLS-1$ buffer.append(Util.timeEnd(remainingTime)); } buffer.append(']'); return buffer.toString(); } private long getRemainingTime(long elapsed) { return (long) ((((double)elapsed) / this.count) * (this.all - this.count)); } private long getElapsed() { return System.currentTimeMillis() - this.start; } } // Failure threshold public static final int DEFAULT_FAILURE_THRESHOLD = 10; int failure_threshold = DEFAULT_FAILURE_THRESHOLD; public PerformanceResults(PrintStream stream) { super(null, null); this.printStream = stream; this.dbRequired = false; setDefaults(); } public PerformanceResults(String name, String baseline, String baselinePrefix, PrintStream stream) { super(null, name); this.baselineName = baseline; this.baselinePrefix = baselinePrefix; this.printStream = stream; this.dbRequired = true; setDefaults(); } /** * Returns the list of all builds currently read. * * @return The names list of all currently known builds */ public String[] getAllBuildNames() { if (this.allBuildNames == null) { setAllBuildNames(); } return this.allBuildNames; } /** * Returns the name of the baseline used for extracted results * * @return The build name of the baseline of null * if no specific baseline is used for the extracted results. */ public String getBaselineName() { return this.baselineName; } /* * Get the baseline prefix (computed from #baselineName). */ String getBaselinePrefix() { return this.baselinePrefix; } /* * Get the build date (see #getBuildDate(String, String)). */ public String getBuildDate() { String buildName = getName(); if (buildName == null) return ""; //$NON-NLS-1$ return Util.getBuildDate(getName(), getBaselinePrefix()); } /** * Return the list of components concerned by performance results. * * @return The list of the components */ public String[] getComponents() { return this.components; } /** * Get the scenarios of a given component. * * @param componentName The component name. Should not be null * @return A list of {@link ScenarioResults scenario results} */ public List getComponentScenarios(String componentName) { ComponentResults componentResults = (ComponentResults) getResults(componentName); if (componentResults == null) return null; return Collections.unmodifiableList(componentResults.children); } /** * Get the scenarios which have a summary for a given component. * * @param componentName The component name * @param config Configuration name * @return A list of {@link ScenarioResults scenario results} which have a summary */ public List getComponentSummaryScenarios(String componentName, String config) { if (componentName == null) { int size = size(); List scenarios = new ArrayList(); for (int i=0; i< size; i++) { ComponentResults componentResults = (ComponentResults) this.children.get(i); scenarios.addAll(componentResults.getSummaryScenarios(true, config)); } return scenarios; } ComponentResults componentResults = (ComponentResults) getResults(componentName); return componentResults.getSummaryScenarios(false, config); } /** * Return the configuration boxes considered for this performance results * sorted or not depending on the given flag. * * @param sort Indicates whether the list must be sorted or not. * The order is defined by the configuration names, not by the box names * @return The list of configuration boxes sorted by configuration names */ public String[] getConfigBoxes(boolean sort) { return sort ? this.sortedConfigDescriptions : this.configDescriptions; } /** * Return the configuration names considered for this performance results * sorted or not depending on the given flag. * * @param sort Indicates whether the list must be sorted or not * @return The list of configuration names */ public String[] getConfigNames(boolean sort) { return sort ?this.sortedConfigNames : this.configNames; } /* * Compute a SQL pattern from all stored configuration names. * For example 'eclipseperflnx1', 'eclipseperflnx2' and 'eclipseperflnx3' * will return 'eclipseperflnx_'. */ String getConfigurationsPattern() { if (this.configPattern == null) { int length = this.sortedConfigNames == null ? 0 : this.sortedConfigNames.length; if (length == 0) return null; this.configPattern = this.sortedConfigNames[0]; int refLength = this.configPattern.length(); for (int i=1; i cannot find a pattern for (int j=0; j cannot find a pattern newConfig.append(this.configPattern.substring(0, j)); } newConfig.append('_'); } else if (newConfig != null) { newConfig.append(c); } } if (newConfig != null) { this.configPattern = newConfig.toString(); } } } return this.configPattern; } /** * Return the name of the last build name except baselines. * * @return the name of the last build */ public String getLastBuildName() { return getLastBuildName(1/*all except baselines*/); } /** * Return the name of the last build name * * @param kind Decide what kind of build is taken into account * 0: all kind of build * 1: all except baseline builds * 2: all except baseline and nightly builds * 3: only integration builds * @return the name of the last build of the selected kind */ public String getLastBuildName(int kind) { if (this.name == null) { getAllBuildNames(); // init build names if necessary int idx = this.allBuildNames.length-1; this.name = this.allBuildNames[idx]; if (kind > 0) { loop: while (idx-- >= 0) { switch (this.name.charAt(0)) { case 'N': if (kind < 2) break loop; break; case 'M': if (kind < 3) break loop; break; case 'I': if (kind < 4) break loop; break; } this.name = this.allBuildNames[idx]; } } } return this.name; } public String getName() { if (this.name == null) { setAllBuildNames(); } return this.name; } /* * (non-Javadoc) * @see org.eclipse.test.internal.performance.results.AbstractResults#getPerformance() */ PerformanceResults getPerformance() { return this; } /** * Get the results of a given scenario. * * @param scenarioName The scenario name * @return The {@link ScenarioResults scenario results} */ public ScenarioResults getScenarioResults(String scenarioName) { ComponentResults componentResults = (ComponentResults) getResults(DB_Results.getComponentNameFromScenario(scenarioName)); return componentResults == null ? null : (ScenarioResults) componentResults.getResults(scenarioName); } /* * Init configurations from performance results database. */ private void initConfigs() { // create config names this.configNames = DB_Results.getConfigs(); this.configDescriptions = DB_Results.getConfigDescriptions(); int length = this.configNames.length; this.sortedConfigNames = new String[length]; for (int i = 0; i < length; i++) { this.sortedConfigNames[i] = this.configNames[i]; } // Sort the config names Arrays.sort(this.sortedConfigNames); this.sortedConfigDescriptions = new String[length]; for (int i=0; inull, * then all known configurations ({@link DB_Results#getConfigs()}) are read. * @param pattern The pattern of the concerned scenarios * @param dataDir The directory where data will be read/stored locally. * If null, then database will be read instead and no storage * will be performed * @param threshold The failure percentage threshold over which a build result * value compared to the baseline is considered as failing. * @param monitor The progress monitor * * @return All known build names */ public String[] readAll(String buildName, String[][] configs, String pattern, File dataDir, int threshold, IProgressMonitor monitor) { // Init this.scenarioPattern = pattern == null ? "%" : pattern; //$NON-NLS-1$ this.failure_threshold = threshold; SubMonitor subMonitor = SubMonitor.convert(monitor, 1000); // Set default names setDefaults(); // Read local data files first reset(dataDir); String[] names = read(true, null, configs, true, dataDir, null, subMonitor.newChild(100)); if (names==null) { // if one local files is missing then force a full DB read! // TODO moderate this to force the DB read only for the missing file... return read(false, null, configs, true, dataDir, null, subMonitor.newChild(900)); } // Search build name in read data boolean buildMissing = true; if (buildName != null) { this.name = buildName; buildMissing = Arrays.binarySearch(names, buildName, Util.BUILD_DATE_COMPARATOR) < 0; } // Look for missing builds if (buildMissing) { String[] builds = DB_Results.getBuilds(); Arrays.sort(builds, Util.BUILD_DATE_COMPARATOR); for (int i=builds.length-1; i>=0; i--) { if (Arrays.binarySearch(names, builds[i], Util.BUILD_DATE_COMPARATOR) >= 0) { break; } read(false, builds[i], configs, true, dataDir, null, subMonitor.newChild(900)); } } return this.allBuildNames; } /** * Read all data from performance database for the given configurations * and scenario pattern. * * Note that calling this method flush all previous read data. * * @param dataDir The directory where local files are located * @param monitor The progress monitor */ public void readLocal(File dataDir, IProgressMonitor monitor) { // Print title String taskName = "Read local performance results"; //$NON-NLS-1$ println(taskName); // Create monitor SubMonitor subMonitor = SubMonitor.convert(monitor, 1000); subMonitor.setTaskName(taskName); // Read reset(dataDir); read(true, null, null, true, dataDir, taskName, subMonitor); } void readLocalFile(File dir) { if (!dir.exists()) return; File dataFile = new File(dir, "performances.dat"); //$NON-NLS-1$ if (!dataFile.exists()) return; DataInputStream stream = null; try { // Read local file info stream = new DataInputStream(new BufferedInputStream(new FileInputStream(dataFile))); // Read build info String str = stream.readUTF(); this.needToUpdateLocalFile = this.name == null || Util.getBuildDate(this.name).compareTo(Util.getBuildDate(str)) > 0; if (this.name != null && Util.getBuildDate(this.name).compareTo(Util.getBuildDate(str)) >= 0) { return; } println(" - read performance results local files info: "); //$NON-NLS-1$ println(" + name : "+str); this.name = str == "" ? null : str; str = stream.readUTF(); println(" + baseline : "+str); if (this.baselineName == null) { this.baselineName = str == "" ? null : str; } str = stream.readUTF(); println(" + baseline prefix: "+str); this.baselinePrefix = str == "" ? null : str; // Write configs info int length = stream.readInt(); println(" + "+length+" configs"); this.configNames = new String[length]; this.sortedConfigNames = new String[length]; this.configDescriptions = new String[length]; this.sortedConfigDescriptions = new String[length]; for (int i = 0; i < length; i++) { this.configNames[i] = stream.readUTF(); this.sortedConfigNames[i] = this.configNames[i]; this.configDescriptions[i] = stream.readUTF(); this.sortedConfigDescriptions[i] = this.configDescriptions[i]; } DB_Results.setConfigs(this.configNames); DB_Results.setConfigDescriptions(this.configDescriptions); // Write builds info length = stream.readInt(); println(" + "+length+" builds"); this.allBuildNames = new String[length]; for (int i = 0; i < length; i++) { this.allBuildNames[i] = stream.readUTF(); } // Write scenarios info length = stream.readInt(); println(" + "+length+" components"); this.components = new String[length]; this.allScenarios = new HashMap(); for (int i = 0; i < length; i++) { this.components[i] = stream.readUTF(); int size = stream.readInt(); List scenarios = new ArrayList(size); for (int j=0; j read from file "+dataFile); //$NON-NLS-1$ } catch (IOException ioe) { println(" !!! "+dataFile+" should be deleted as it contained invalid data !!!"); //$NON-NLS-1$ //$NON-NLS-2$ } finally { try { stream.close(); } catch (IOException e) { // nothing else to do! } } } private int readScenarios(String buildName, SubMonitor subMonitor) throws OperationCanceledException { subMonitor.setWorkRemaining(10); long start = System.currentTimeMillis(); String titleSuffix; if (buildName == null) { titleSuffix = "all database scenarios..."; //$NON-NLS-1$ } else { titleSuffix = "all database scenarios for "+buildName+" build..."; //$NON-NLS-1$ //$NON-NLS-2$ } print(" + get "+titleSuffix); //$NON-NLS-1$ subMonitor.subTask("Get "+titleSuffix); //$NON-NLS-1$ this.allScenarios = DB_Results.queryAllScenarios(this.scenarioPattern, buildName); if (this.allScenarios == null) return -1; int allScenariosSize = 0; List componentsSet = new ArrayList(this.allScenarios.keySet()); Collections.sort(componentsSet); int componentsSize = componentsSet.size(); componentsSet.toArray(this.components = new String[componentsSize]); for (int i=0; i "+allScenariosSize+" found in "+(System.currentTimeMillis()-start)+"ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ subMonitor.worked(10); if (subMonitor.isCanceled()) throw new OperationCanceledException(); return allScenariosSize; } void reset(File dataDir) { this.allBuildNames = null; this.children = new ArrayList(); // this.name = null; this.components = null; this.allScenarios = null; readLocalFile(dataDir); } private void setAllBuildNames() { SortedSet builds = new TreeSet(Util.BUILD_DATE_COMPARATOR); int size = size(); if (size == 0) return; for (int i=0; i 0) { int n = 0; Iterator buildNames = builds.iterator(); while (buildNames.hasNext()) { String buildName = (String) buildNames.next(); if (this.lastBuildName == null || Util.getBuildDate(buildName).compareTo(Util.getBuildDate(this.lastBuildName)) <= 0) { this.allBuildNames[n++] = buildName; } } if (n < buildsSize) { System.arraycopy(this.allBuildNames, 0, this.allBuildNames = new String[n], 0, n); } int idx = n-1; String lastBuild = this.allBuildNames[idx--]; while (lastBuild.startsWith(DB_Results.getDbBaselinePrefix())) { lastBuild = this.allBuildNames[idx--]; } this.needToUpdateLocalFile = this.name == null || Util.getBuildDate(lastBuild).compareTo(Util.getBuildDate(this.name)) > 0; this.name = lastBuild; if (this.baselineName != null) { String lastBuildDate = Util.getBuildDate(lastBuild); if (Util.getBuildDate(this.baselineName).compareTo(lastBuildDate) > 0) { this.baselineName = DB_Results.getLastBaselineBuild(lastBuildDate); } } } } private void setConfigInfo(String[][] configs) { if (configs == null) return; // Store config information int length = configs.length; this.configNames = new String[length]; this.sortedConfigNames = new String[length]; this.configDescriptions = new String[length]; for (int i=0; i use last one: "+this.name); //$NON-NLS-1$ } } } // Init baseline name if not set if (this.baselineName == null && getName() != null) { String buildDate = Util.getBuildDate(getName()); this.baselineName = DB_Results.getLastBaselineBuild(buildDate); if (this.baselineName == null && this.dbRequired) { throw new RuntimeException("Cannot find any baseline to refer!"); //$NON-NLS-1$ } if (this.printStream != null) { this.printStream.println(" + no baseline specified => use last one: "+this.baselineName); //$NON-NLS-1$ } } // Init baseline prefix if not set if (this.baselinePrefix == null && this.baselineName != null) { // Assume that baseline name format is *always* x.y_yyyyMMddhhmm_yyyyMMddhhmm int index = this.baselineName.lastIndexOf('_'); if (index > 0) { this.baselinePrefix = this.baselineName.substring(0, index); } else { this.baselinePrefix = DB_Results.getDbBaselinePrefix(); } } // Set scenario pattern default if (this.scenarioPattern == null) { this.scenarioPattern = "%"; //$NON-NLS-1$ } // Flush print stream if (this.printStream != null) { this.printStream.println(); this.printStream.flush(); } } public void setLastBuildName(String lastBuildName) { this.lastBuildName = lastBuildName; // if (lastBuildName == null) { // int idx = this.allBuildNames.length-1; // String lastBuild = this.allBuildNames[idx--]; // while (this.name.startsWith(DB_Results.getDbBaselinePrefix())) { // lastBuild = this.allBuildNames[idx--]; // } // this.name = lastBuild; // } else { // this.name = lastBuildName; // } } /** * Update a given build information with database contents. * * @param builds The builds to read new data * @param force Force the update from the database, even if the build is * already known. * @param dataDir The directory where data should be stored locally if necessary. * If null, then information changes won't be persisted. * @param monitor The progress monitor * @return All known builds */ public String[] updateBuilds(String[] builds, boolean force, File dataDir, IProgressMonitor monitor) { // Print title StringBuffer buffer = new StringBuffer("Update data for "); //$NON-NLS-1$ int length = builds == null ? 0 : builds.length; switch (length) { case 0: buffer.append("all builds"); //$NON-NLS-1$ reset(dataDir); break; case 1: buffer.append("one build"); //$NON-NLS-1$ break; default: buffer.append("several builds"); //$NON-NLS-1$ break; } String taskName = buffer.toString(); println(buffer); // Create sub-monitor SubMonitor subMonitor = SubMonitor.convert(monitor, 1000*length); subMonitor.setTaskName(taskName); // Read for (int i=0; inull, then information changes won't be persisted. * @param monitor The progress monitor * @return All known builds */ public String[] updateBuild(String buildName, boolean force, File dataDir, IProgressMonitor monitor) { // Print title StringBuffer buffer = new StringBuffer("Update data for "); //$NON-NLS-1$ if (buildName == null) { buffer.append("all builds"); //$NON-NLS-1$ reset(dataDir); } else { buffer.append("one build"); //$NON-NLS-1$ } String taskName = buffer.toString(); println(buffer); // Create sub-monitor SubMonitor subMonitor = SubMonitor.convert(monitor, 1000); subMonitor.setTaskName(taskName); // Read read(false, buildName, null, force, dataDir, taskName, subMonitor); // Refresh name if (buildName != null && !buildName.startsWith(DB_Results.getDbBaselinePrefix())) { this.name = buildName; } // Return new list all build names return this.allBuildNames; } /* * Write general information. */ void writeData(File dir) { if (!DB_Results.DB_CONNECTION) { // Only write new local file if there's a database connection // otherwise contents may not be complete... return; } if (dir ==null || (!dir.exists() && !dir.mkdirs())) { System.err.println("can't create directory " + dir); //$NON-NLS-1$ return; } File dataFile = new File(dir, "performances.dat"); //$NON-NLS-1$ if (dataFile.exists()) { if (!this.needToUpdateLocalFile) { return; } dataFile.delete(); } try { DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile))); // Write build info stream.writeUTF(this.name == null ? DB_Results.getLastCurrentBuild() : this.name); stream.writeUTF(this.baselineName == null ? DB_Results.getLastBaselineBuild(null) : this.baselineName); stream.writeUTF(this.baselinePrefix == null ? "" : this.baselinePrefix); // Write configs info int length = this.sortedConfigNames.length; stream.writeInt(length); for (int i = 0; i < length; i++) { stream.writeUTF(this.sortedConfigNames[i]); stream.writeUTF(this.sortedConfigDescriptions[i]); } // Write builds info String[] builds = this.allBuildNames == null ? DB_Results.getBuilds() : this.allBuildNames; length = builds.length; stream.writeInt(length); for (int i = 0; i < length; i++) { stream.writeUTF(builds[i]); } // Write scenarios info length = this.components.length; stream.writeInt(length); for (int i = 0; i < length; i++) { stream.writeUTF(this.components[i]); List scenarios = (List) this.allScenarios.get(this.components[i]); int size = scenarios.size(); stream.writeInt(size); for (int j=0; j performance results general data written in file " + dataFile); //$NON-NLS-1$ } catch (FileNotFoundException e) { System.err.println("can't create output file" + dataFile); //$NON-NLS-1$ } catch (IOException e) { e.printStackTrace(); } } }