blob: 010bb21994e899d29a26c5872e402474788474f7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 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.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.test.internal.performance.eval.StatisticsUtil;
import org.eclipse.test.internal.performance.results.db.AbstractResults;
import org.eclipse.test.internal.performance.results.db.BuildResults;
import org.eclipse.test.internal.performance.results.db.ConfigResults;
import org.eclipse.test.internal.performance.results.db.DB_Results;
import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
import org.eclipse.test.internal.performance.results.utils.Util;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.views.properties.ComboBoxPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.PropertyDescriptor;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;
import org.osgi.service.prefs.BackingStoreException;
/**
* An Organization Element
*/
public abstract class ResultsElement implements IAdaptable, IPropertySource, IWorkbenchAdapter, Comparable<ResultsElement> {
// Image descriptors
private static final ISharedImages WORKBENCH_SHARED_IMAGES = PlatformUI.getWorkbench().getSharedImages();
public static final Image ERROR_IMAGE = WORKBENCH_SHARED_IMAGES.getImage(ISharedImages.IMG_OBJS_ERROR_TSK);
public static final ImageDescriptor ERROR_IMAGE_DESCRIPTOR = WORKBENCH_SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_OBJS_ERROR_TSK);
public static final Image WARN_IMAGE = WORKBENCH_SHARED_IMAGES.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
public static final ImageDescriptor WARN_IMAGE_DESCRIPTOR = WORKBENCH_SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_OBJS_WARN_TSK);
public static final Image INFO_IMAGE = WORKBENCH_SHARED_IMAGES.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
public static final ImageDescriptor INFO_IMAGE_DESCRIPTOR = WORKBENCH_SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK);
public static final Image HELP_IMAGE = WORKBENCH_SHARED_IMAGES.getImage(ISharedImages.IMG_LCL_LINKTO_HELP);
public static final ImageDescriptor HELP_IMAGE_DESCRIPTOR = WORKBENCH_SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_LCL_LINKTO_HELP);
public static final ImageDescriptor FOLDER_IMAGE_DESCRIPTOR = WORKBENCH_SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER);
public static final ImageDescriptor CONNECT_IMAGE_DESCRIPTOR = WORKBENCH_SHARED_IMAGES.getImageDescriptor(ISharedImages.IMG_ELCL_SYNCED);
// Model
ResultsElement parent;
AbstractResults results;
ResultsElement[] children;
String name;
int status = -1;
// Stats
double[] statistics;
// Status constants
// state
static final int UNKNOWN = 0x01;
static final int UNREAD = 0x02;
static final int READ = 0x04;
static final int MISSING = 0x08;
public static final int STATE_MASK = 0x0F;
// info
static final int SMALL_VALUE = 0x0010;
static final int STUDENT_TTEST = 0x0020;
public static final int INFO_MASK = 0x0030;
// warning
static final int NO_BASELINE = 0x0040;
static final int SINGLE_RUN = 0x0080;
static final int BIG_ERROR = 0x0100;
static final int NOT_STABLE = 0x0200;
static final int NOT_RELIABLE = 0x0400;
public static final int WARNING_MASK = 0x0FC0;
// error
static final int BIG_DELTA = 0x1000;
public static final int ERROR_MASK = 0xF000;
// Property descriptors
static final String P_ID_STATUS_INFO = "ResultsElement.status_info"; //$NON-NLS-1$
static final String P_ID_STATUS_WARNING = "ResultsElement.status_warning"; //$NON-NLS-1$
static final String P_ID_STATUS_ERROR = "ResultsElement.status_error"; //$NON-NLS-1$
static final String P_ID_STATUS_COMMENT = "ResultsElement.status_comment"; //$NON-NLS-1$
static final String P_STR_STATUS_INFO = " info"; //$NON-NLS-1$
static final String P_STR_STATUS_WARNING = "warning"; //$NON-NLS-1$
static final String P_STR_STATUS_ERROR = "error"; //$NON-NLS-1$
static final String P_STR_STATUS_COMMENT = "comment"; //$NON-NLS-1$
static final String[] NO_VALUES = new String[0];
private static Vector<IPropertyDescriptor> DESCRIPTORS;
static final TextPropertyDescriptor COMMENT_DESCRIPTOR = new TextPropertyDescriptor(P_ID_STATUS_COMMENT, P_STR_STATUS_COMMENT);
static final TextPropertyDescriptor ERROR_DESCRIPTOR = new TextPropertyDescriptor(P_ID_STATUS_ERROR, P_STR_STATUS_ERROR);
static Vector<IPropertyDescriptor> initDescriptors(int status) {
DESCRIPTORS = new Vector<>();
// Status category
DESCRIPTORS.add(getInfosDescriptor(status));
DESCRIPTORS.add(getWarningsDescriptor(status));
DESCRIPTORS.add(ERROR_DESCRIPTOR);
ERROR_DESCRIPTOR.setCategory("Status");
// Survey category
DESCRIPTORS.add(COMMENT_DESCRIPTOR);
COMMENT_DESCRIPTOR.setCategory("Survey");
return DESCRIPTORS;
}
static Vector<IPropertyDescriptor> getDescriptors() {
return DESCRIPTORS;
}
static ComboBoxPropertyDescriptor getInfosDescriptor(int status) {
List<String> list = new ArrayList<>();
if ((status & SMALL_VALUE) != 0) {
list.add("Some builds have tests with small values");
}
if ((status & STUDENT_TTEST) != 0) {
list.add("Some builds have student-t test error over the threshold");
}
String[] infos = new String[list.size()];
if (list.size() > 0) {
list.toArray(infos);
}
ComboBoxPropertyDescriptor infoDescriptor = new ComboBoxPropertyDescriptor(P_ID_STATUS_INFO, P_STR_STATUS_INFO, infos);
infoDescriptor.setCategory("Status");
return infoDescriptor;
}
static PropertyDescriptor getWarningsDescriptor(int status) {
List<String> list = new ArrayList<>();
if ((status & BIG_ERROR) != 0) {
list.add("Some builds have tests with error over 3%");
}
if ((status & NOT_RELIABLE) != 0) {
list.add("Some builds have no reliable tests");
}
if ((status & NOT_STABLE) != 0) {
list.add("Some builds have no stable tests");
}
if ((status & NO_BASELINE) != 0) {
list.add("Some builds have no baseline to compare with");
}
if ((status & SINGLE_RUN) != 0) {
list.add("Some builds have single run tests");
}
String[] warnings = new String[list.size()];
if (list.size() > 0) {
list.toArray(warnings);
}
ComboBoxPropertyDescriptor warningDescriptor = new ComboBoxPropertyDescriptor(P_ID_STATUS_WARNING, P_STR_STATUS_WARNING, warnings);
warningDescriptor.setCategory("Status");
return warningDescriptor;
}
ResultsElement() {
}
ResultsElement(AbstractResults results, ResultsElement parent) {
this.parent = parent;
this.results = results;
}
ResultsElement(String name, ResultsElement parent) {
this.parent = parent;
this.name = name;
}
@Override
public int compareTo(ResultsElement o) {
if (this.results == null) {
if (o != null && this.name != null) {
ResultsElement element = o;
return this.name.compareTo(element.getName());
}
return -1;
}
if (o != null) {
return this.results.compareTo(o.results);
}
return -1;
}
abstract ResultsElement createChild(AbstractResults testResults);
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == IPropertySource.class) {
return (T) this;
}
if (adapter == IWorkbenchAdapter.class) {
return (T) this;
}
return null;
}
/**
* Iterate the element children.
*/
public ResultsElement[] getChildren() {
if (this.results == null) {
return new ResultsElement[0];
}
if (this.children == null) {
initChildren();
}
return this.children;
}
@Override
public Object[] getChildren(Object o) {
if (this.results == null) {
return new Object[0];
}
if (this.children == null) {
initChildren();
}
return this.children;
}
@Override
public Object getEditableValue() {
return this;
}
final String getId() {
return getId(new StringBuffer()).toString();
}
private StringBuffer getId(StringBuffer buffer) {
if (this.parent != null) {
return this.parent.getId(buffer).append('/').append(getName());
}
return buffer.append(DB_Results.getDbName());
}
@Override
public ImageDescriptor getImageDescriptor(Object object) {
if (object instanceof ResultsElement) {
ResultsElement resultsElement = (ResultsElement) object;
// DEBUG
// if (resultsElement.getName().equals("I20090806-0100")) {
// if (resultsElement.results != null) {
// String toString = resultsElement.results.getParent().toString();
// String toString = resultsElement.results.toString();
// if (toString.indexOf("testStoreExists")>0 && toString.indexOf("eplnx2")>0) {
// System.out.println("stop");
// }
// }
// }
int elementStatus = resultsElement.getStatus();
if (elementStatus == MISSING) {
return HELP_IMAGE_DESCRIPTOR;
}
if ((elementStatus & ResultsElement.ERROR_MASK) != 0) {
return ERROR_IMAGE_DESCRIPTOR;
}
if ((elementStatus & ResultsElement.WARNING_MASK) != 0) {
return WARN_IMAGE_DESCRIPTOR;
}
if ((elementStatus & ResultsElement.INFO_MASK) != 0) {
return INFO_IMAGE_DESCRIPTOR;
}
}
return null;
}
@Override
public String getLabel(Object o) {
return getName();
}
/**
* Returns the name
*/
public String getName() {
if (this.name == null && this.results != null) {
this.name = this.results.getName();
}
return this.name;
}
/**
* Returns the parent
*/
@Override
public Object getParent(Object o) {
return this.parent;
}
@Override
public IPropertyDescriptor[] getPropertyDescriptors() {
Vector<IPropertyDescriptor> descriptors = getDescriptors();
if (descriptors == null) {
descriptors = initDescriptors(getStatus());
}
int size = descriptors.size();
IPropertyDescriptor[] descriptorsArray = new IPropertyDescriptor[size];
descriptorsArray[0] = getInfosDescriptor(getStatus());
descriptorsArray[1] = getWarningsDescriptor(getStatus());
for (int i=2; i<size; i++) {
descriptorsArray[i] = descriptors.get(i);
}
return descriptorsArray;
}
@Override
public Object getPropertyValue(Object propKey) {
if (propKey.equals(P_ID_STATUS_INFO)) {
if ((getStatus() & INFO_MASK) != 0) {
return Integer.valueOf(0);
}
}
if (propKey.equals(P_ID_STATUS_WARNING)) {
if ((getStatus() & WARNING_MASK) != 0) {
return Integer.valueOf(0);
}
}
if (propKey.equals(P_ID_STATUS_ERROR)) {
if ((getStatus() & BIG_DELTA) != 0) {
return "Some builds have tests with regression";
}
}
if (propKey.equals(P_ID_STATUS_COMMENT)) {
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(IPerformancesConstants.PLUGIN_ID);
return preferences.get(getId(), "");
}
return null;
}
public ResultsElement getResultsElement(String resultName) {
int length = getChildren(null).length;
for (int i=0; i<length; i++) {
ResultsElement searchedResults = this.children[i];
if (searchedResults.getName().equals(resultName)) {
return searchedResults;
}
}
return null;
}
/**
* Return the status of the element.
*
* The status is a bit mask pattern where digits are
* allowed as follow:
* <ul>
* <li>0-3: bits for state showing whether the element is
* <ul>
* <li>{@link #UNKNOWN} : not connected to a db</li>
* <li>{@link #UNREAD} : is not valid (e.g. NaN results)</li>
* <li>{@link #MISSING} : no results (e.g. the perf machine crashed and didn't store any results)</li>
* <li>{@link #READ} : has valid results</li>
* </ul>
* </li>
* <li>4-5: bits for information. Current possible information are
* <ul>
* <li>{@link #SMALL_VALUE} : build results or delta with baseline value is under 100ms</li>
* <li>{@link #STUDENT_TTEST} : the Student T-test is over the threshold (old yellow color for test results)</li>
* </ul>
* </li>
* <li>6-11: bits for warnings. Current possible warnings are
* <ul>
* <li>{@link #NO_BASELINE} : no baseline for the current build</li>
* <li>{@link #SINGLE_RUN} : the test has only one run (i.e. no error could be computed), hence its reliability cannot be evaluated</li>
* <li>{@link #BIG_ERROR} : the test result is over the 3% threshold</li>
* <li>{@link #NOT_STABLE} : the test history shows a deviation between 10% and 20% (may mean that this test is not so reliable)</li>
* <li>{@link #NOT_RELIABLE} : the test history shows a deviation over 20% (surely means that this test is too erratic to be reliable)</li>
* </ul>
* </li>
* <li>12-15: bits for errors. Current possible errors are
* <ul>
* <li>{@link #BIG_DELTA} : the delta for the test is over the 10% threshold</li>
* </ul>
* </li>
* </ul>
*
* Note that these explanation applied to {@link BuildResultsElement}, and {@link DimResultsElement}.
* For {@link ComponentResultsElement}, and {@link ScenarioResultsElement}, it's the merge of all the children status
* and means "Some tests have..." instead of "The test has...". For {@link ConfigResultsElement}, it means the status
* of the most recent build compared to its most recent baseline.
*
* @return An int with each bit set when the corresponding symptom applies.
*/
public final int getStatus() {
if (this.status < 0) {
initStatus();
}
return this.status;
}
/**
* Return the statistics of the build along its history.
*
* @return An array of double built as follows:
* <ul>
* <li>0: numbers of values</li>
* <li>1: mean of values</li>
* <li>2: standard deviation of these values</li>
* <li>3: coefficient of variation of these values</li>
* </ul>
*/
double[] getStatistics() {
return this.statistics;
}
/**
* Returns whether the element (or one in its hierarchy) has an error.
*
* @return <code> true</code> if the element or one in its hierarchy has an error,
* <code> false</code> otherwise
*/
public final boolean hasError() {
return (getStatus() & ERROR_MASK) != 0;
}
void initChildren() {
AbstractResults[] resultsChildren = this.results.getChildren();
int length = resultsChildren.length;
this.children = new ResultsElement[length];
int count = 0;
for (int i=0; i<length; i++) {
ResultsElement childElement = createChild(resultsChildren[i]);
if (childElement != null) {
this.children[count++] = childElement;
}
}
if (count < length) {
System.arraycopy(this.children, 0, this.children = new ResultsElement[count], 0, count);
}
}
void initStatus() {
this.status = READ;
if (this.results != null) {
if (this.children == null) initChildren();
int length = this.children.length;
for (int i=0; i<length; i++) {
this.status |= this.children[i].getStatus();
}
}
}
int initStatus(BuildResults buildResults) {
this.status = READ;
// Get values
double buildValue = buildResults.getValue();
ConfigResults configResults = (ConfigResults) buildResults.getParent();
BuildResults baselineResults = configResults.getBaselineBuildResults(buildResults.getName());
double baselineValue = baselineResults.getValue();
double delta = (baselineValue - buildValue) / baselineValue;
// Store if there's no baseline
if (Double.isNaN(delta)) {
this.status |= NO_BASELINE;
}
// Store if there's only one run
long baselineCount = baselineResults.getCount();
long currentCount = buildResults.getCount();
double error = Double.NaN;
if (baselineCount == 1 || currentCount == 1) {
this.status |= SINGLE_RUN;
}
// Store if the T-test is not good
double ttestValue = Util.computeTTest(baselineResults, buildResults);
int degreeOfFreedom = (int) (baselineResults.getCount()+buildResults.getCount()-2);
if (ttestValue >= 0 && StatisticsUtil.getStudentsT(degreeOfFreedom, StatisticsUtil.T90) >= ttestValue) {
this.status |= STUDENT_TTEST;
}
// Store if there's a big error (over 3%)
double baselineError = baselineResults.getError();
double currentError = buildResults.getError();
error = Double.isNaN(baselineError)
? currentError / baselineValue
: Math.sqrt(baselineError*baselineError + currentError*currentError) / baselineValue;
if (error > 0.03) {
this.status |= BIG_ERROR;
}
// Store if there's a big delta (over 10%)
if (delta <= -0.1) {
this.status |= BIG_DELTA;
double currentBuildValue = buildResults.getValue();
double diff = Math.abs(baselineValue - currentBuildValue);
if (currentBuildValue < 100 || diff < 100) { // moderate the status when
// diff is less than 100ms
this.status |= SMALL_VALUE;
} else {
double[] stats = getStatistics();
if (stats != null) {
if (stats[3] > 0.2) { // invalidate the status when the test
// historical deviation is over 20%
this.status |= NOT_RELIABLE;
} else if (stats[3] > 0.1) { // moderate the status when the test
// historical deviation is between 10%
// and 20%
this.status |= NOT_STABLE;
}
}
}
}
return this.status;
}
public boolean isInitialized() {
return this.results != null;
}
@Override
public boolean isPropertySet(Object property) {
return false;
}
boolean onlyFingerprints() {
if (this.parent != null) {
return this.parent.onlyFingerprints();
}
return ((PerformanceResultsElement)this).fingerprints;
}
@Override
public void resetPropertyValue(Object property) {
}
void resetStatus() {
this.status = -1;
if (this.results != null) {
if (this.children == null) initChildren();
int length = this.children.length;
for (int i=0; i<length; i++) {
this.children[i].resetStatus();
}
}
}
@Override
public void setPropertyValue(Object name, Object value) {
if (name.equals(P_ID_STATUS_COMMENT)) {
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(IPerformancesConstants.PLUGIN_ID);
preferences.put(getId(), (String) value);
try {
preferences.flush();
} catch (BackingStoreException e) {
// skip
}
}
}
/**
* Sets the image descriptor
*/
void setImageDescriptor(ImageDescriptor desc) {
// this.imageDescriptor = desc;
}
@Override
public String toString() {
if (this.results == null) {
return getName();
}
return this.results.toString();
}
/*
* Write the failures of the element in the given buffer
*/
StringBuffer getFailures(StringBuffer buffer, int kind, StringBuffer excluded) {
int length = getChildren().length;
for (int i=0; i<length; i++) {
this.children[i].getFailures(buffer, kind, excluded);
}
return buffer;
}
}