blob: 7d54accb84604e4db607d98c046adb34254251a8 [file] [log] [blame]
/*******************************************************************************
* 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.performance.ui;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.test.internal.performance.PerformanceTestPlugin;
import org.eclipse.test.internal.performance.db.Variations;
import org.eclipse.test.internal.performance.results.utils.Util;
import org.osgi.framework.Bundle;
public class Utils {
public final static double STANDARD_ERROR_THRESHOLD = 0.03; // 3%
static final NumberFormat PERCENT_FORMAT = NumberFormat.getPercentInstance();
static {
PERCENT_FORMAT.setMaximumFractionDigits(1);
}
static final DecimalFormat DEVIATION_FORMAT = (DecimalFormat) NumberFormat.getPercentInstance();
static {
DEVIATION_FORMAT.setMaximumFractionDigits(1);
DEVIATION_FORMAT.setMinimumFractionDigits(1);
DEVIATION_FORMAT.setPositivePrefix("+");
DEVIATION_FORMAT.setNegativePrefix("- ");
}
static final DecimalFormat STDERR_FORMAT = (DecimalFormat) NumberFormat.getNumberInstance();
static {
STDERR_FORMAT.setMaximumFractionDigits(1);
STDERR_FORMAT.setMinimumFractionDigits(1);
STDERR_FORMAT.setMultiplier(100);
}
public final static String STANDARD_ERROR_THRESHOLD_STRING = PERCENT_FORMAT.format(STANDARD_ERROR_THRESHOLD);
// Image files
public final static String UNKNOWN_IMAGE = "images/Unknown.gif";
public final static String OK_IMAGE = "images/OK.gif";
public final static String OK_IMAGE_WARN = "images/OK_caution.gif";
public final static String FAIL_IMAGE = "images/FAIL.gif";
public final static String FAIL_IMAGE_WARN = "images/FAIL_caution.gif";
public final static String FAIL_IMAGE_EXPLAINED = "images/FAIL_greyed.gif";
public final static String LIGHT = "images/light.gif";
public final static String WARNING_OBJ = "images/warning_obj.gif";
// Java script files
public final static String TOOLTIP_SCRIPT = "scripts/ToolTip.js";
public final static String TOOLTIP_STYLE = "scripts/ToolTip.css";
public final static String FINGERPRINT_SCRIPT = "scripts/Fingerprints.js";
// Doc files
public final static String HELP = "doc/help.html";
// Status
public final static int OK = 0;
public final static int NAN = 0x1;
public final static int ERR = 0x2;
/**
* Return <html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
*/
public final static String HTML_OPEN = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n";
/**
* Return "&lt;/html&gt;".
*/
public final static String HTML_CLOSE = "</html>\n";
/**
* Default style-sheet used on eclipse.org
*/
public final static String HTML_DEFAULT_CSS = "<style type=\"text/css\">" + "p, table, td, th { font-family: arial, helvetica, geneva; font-size: 10pt}\n" + "pre { font-family: \"Courier New\", Courier, mono; font-size: 10pt}\n"
+ "h2 { font-family: arial, helvetica, geneva; font-size: 18pt; font-weight: bold ; line-height: 14px}\n" + "code { font-family: \"Courier New\", Courier, mono; font-size: 10pt}\n" + "sup { font-family: arial,helvetica,geneva; font-size: 10px}\n"
+ "h3 { font-family: arial, helvetica, geneva; font-size: 14pt; font-weight: bold}\n" + "li { font-family: arial, helvetica, geneva; font-size: 10pt}\n" + "h1 { font-family: arial, helvetica, geneva; font-size: 28px; font-weight: bold}\n"
+ "body { font-family: arial, helvetica, geneva; font-size: 10pt; clip: rect( ); margin-top: 5mm; margin-left: 3mm}\n" + ".indextop { font-size: x-large;; font-family: Verdana, Arial, Helvetica, sans-serif; font-weight: bold}\n"
+ ".indexsub { font-size: xx-small;; font-family: Arial, Helvetica, sans-serif; color: #8080FF}\n" + "</style>\n\n";
/**
* Creates a Variations object using build id pattern, config and jvm.
*
* @param buildIdPattern
* @param config
* @param jvm
*/
public static Variations getVariations(String buildIdPattern, String config, String jvm) {
String buildIdPatterns = buildIdPattern.replace(',', '%');
Variations variations = new Variations();
variations.put(PerformanceTestPlugin.CONFIG, config);
variations.put(PerformanceTestPlugin.BUILD, buildIdPatterns);
variations.put("jvm", jvm);
return variations;
}
/**
* Copy all bundle files contained in the given path
*/
public static void copyBundleFiles(Bundle bundle, String path, String pattern, File output) {
Enumeration imageFiles = bundle.findEntries(path, pattern, false);
while (imageFiles.hasMoreElements()) {
URL url = (URL) imageFiles.nextElement();
try {
File outputFile = new File(output, url.getFile());
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
Util.copyStream(url.openStream(), outputFile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* Downsample Image to 8 bit depth format so that the resulting image data can be saved to GIF. Note. If the source image contains
* photo quality content with more than 256 colours, resulting data will look very poor.
*/
static int closest(RGB[] rgbs, int n, RGB rgb) {
int minDist = 256 * 256 * 3;
int minIndex = 0;
for (int i = 0; i < n; ++i) {
RGB rgb2 = rgbs[i];
int da = rgb2.red - rgb.red;
int dg = rgb2.green - rgb.green;
int db = rgb2.blue - rgb.blue;
int dist = da * da + dg * dg + db * db;
if (dist < minDist) {
minDist = dist;
minIndex = i;
}
}
return minIndex;
}
static class ColorCounter implements Comparable {
RGB rgb;
int count;
public int compareTo(Object o) {
return ((ColorCounter) o).count - this.count;
}
}
public static ImageData downSample(Image image) {
ImageData data = image.getImageData();
if (!data.palette.isDirect && data.depth <= 8)
return data;
// compute a histogram of color frequencies
HashMap freq = new HashMap();
int width = data.width;
int[] pixels = new int[width];
int[] maskPixels = new int[width];
for (int y = 0, height = data.height; y < height; ++y) {
data.getPixels(0, y, width, pixels, 0);
for (int x = 0; x < width; ++x) {
RGB rgb = data.palette.getRGB(pixels[x]);
ColorCounter counter = (ColorCounter) freq.get(rgb);
if (counter == null) {
counter = new ColorCounter();
counter.rgb = rgb;
freq.put(rgb, counter);
}
counter.count++;
}
}
// sort colors by most frequently used
ColorCounter[] counters = new ColorCounter[freq.size()];
freq.values().toArray(counters);
Arrays.sort(counters);
// pick the most frequently used 256 (or fewer), and make a palette
ImageData mask = null;
if (data.transparentPixel != -1 || data.maskData != null) {
mask = data.getTransparencyMask();
}
int n = Math.min(256, freq.size());
RGB[] rgbs = new RGB[n + (mask != null ? 1 : 0)];
for (int i = 0; i < n; ++i)
rgbs[i] = counters[i].rgb;
if (mask != null) {
rgbs[rgbs.length - 1] = data.transparentPixel != -1 ? data.palette.getRGB(data.transparentPixel) : new RGB(255, 255, 255);
}
PaletteData palette = new PaletteData(rgbs);
// create a new image using the new palette:
// for each pixel in the old image, look up the best matching
// index in the new palette
ImageData newData = new ImageData(width, data.height, 8, palette);
if (mask != null)
newData.transparentPixel = rgbs.length - 1;
for (int y = 0, height = data.height; y < height; ++y) {
data.getPixels(0, y, width, pixels, 0);
if (mask != null)
mask.getPixels(0, y, width, maskPixels, 0);
for (int x = 0; x < width; ++x) {
if (mask != null && maskPixels[x] == 0) {
pixels[x] = rgbs.length - 1;
} else {
RGB rgb = data.palette.getRGB(pixels[x]);
pixels[x] = closest(rgbs, n, rgb);
}
}
newData.setPixels(0, y, width, pixels, 0);
}
return newData;
}
/**
* Returns the date/time from the build id in format yyyymmddhm
*
* @param buildId
* @return date/time in format YYYYMMDDHHMM, ie. 200504060010
*/
public static long getDateFromBuildID(String buildId) {
return getDateFromBuildID(buildId, false);
}
public static long getDateFromBuildID(String buildId, boolean matchLast) {
// initialize to "error" value.
Long result = Long.MAX_VALUE;
try {
if (buildId.indexOf('-') > -1) {
// if regular build, expect <buildType><date>-<time> format
String[] buildIdParts = buildId.split("-");
// two parts are from IDs such as I20140606-1215, so skip first
// char, and hour and second are in second part.
if (buildIdParts.length == 2) {
int year = Integer.parseInt(buildIdParts[0].substring(1, 5));
int month = Integer.parseInt(buildIdParts[0].substring(5, 7));
int date = Integer.parseInt(buildIdParts[0].substring(7, 9));
int hours = Integer.parseInt(buildIdParts[1].substring(0, 2));
int min = Integer.parseInt(buildIdParts[1].substring(2, 4));
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, date, hours, min);
result = calendar.getTimeInMillis();
} else if (buildIdParts.length == 3) {
// three parts, such a from R-4.5-201506032000, so only
// part[2] is date. Special case, where we "code" the R-builds with I-build
// date at end, so we know they "go together". Those identifiers look
// like R-4.5-201506032000_201511032000 or similar.
// if no underscore is detected, we use simply "part 2".
// if underscore is detected then further spiting is
// required. In that case, if matchLast is true, we use that to the
// right of underscore. If matchLast is false, we use that to the left of
// underscore. If matchLast is true, but there is no underscore, we
// print a warning, but still return the normal 'part 2'.
int uLocation = buildIdParts[2].indexOf('_');
if (uLocation > -1) {
String firstDateString = buildIdParts[2].substring(0, uLocation - 1);
String secondDateString = buildIdParts[2].substring(uLocation);
if (matchLast) {
result = getDateAsMilliSecs(secondDateString);
} else {
result = getDateAsMilliSecs(firstDateString);
}
} else {
String dateString = buildIdParts[2];
result = getDateAsMilliSecs(dateString);
}
} else {
System.err.println("ERROR: BuildId where number of build parts (based on split(\"-\")) was neither 2 nor 3 parts. Unexpected data. BuildId: " + buildId);
}
} else {
System.err.println("ERROR: BuildId with no '-' found. BuildId: " + buildId + ". Unexpected data.");
}
} catch (StringIndexOutOfBoundsException e) {
// Do nothing, but print warning, and return MAX_VALUE"
System.err.println("ERROR: StringIndexOutofBoundsExeception while processing Date part of buildId, " + buildId + ". Probably due to error in data in database?");
}
// If we "fall through" with no assignment, we return "MAX_VALUE", since
// most (all?) checks of date involve "if less than", so this makes
// those evaluate to false. TODO: should do explicit check for "equals MAX_VALUE" so we
// would know it was an error.
return result;
}
public static Long getDateAsMilliSecs(String dateString) {
// we use "MAX_VALUE" as an error indicator.
Long result = Long.MAX_VALUE;
Pattern expectedFormat = Pattern.compile("\\d{12}");
Matcher matcher = expectedFormat.matcher(dateString);
// we assume strict format on date, such as 201511040800.
// 12 digits only.
if (matcher.matches()) {
int year = Integer.parseInt(dateString.substring(0, 4));
int month = Integer.parseInt(dateString.substring(4, 6));
int date = Integer.parseInt(dateString.substring(6, 8));
int hours = Integer.parseInt(dateString.substring(8, 10));
int min = Integer.parseInt(dateString.substring(10, 12));
Calendar localCalendar = Calendar.getInstance();
localCalendar.set(year, month, date, hours, min);
result = localCalendar.getTimeInMillis();
} else {
System.err.println("ERROR: dateString, " + dateString + ", was not exactly 12 digits in length. Long.MAX_VALUE returned");
}
return result;
}
/**
* Returns a message corresponding to given statistics.
*
* @param resultStats
* The value with its standard error
* @param full
* @return The failure message. May be empty if stats are good...
*/
public static String failureMessage(double[] resultStats, boolean full) {
StringBuffer buffer = new StringBuffer();
int level = confidenceLevel(resultStats);
// boolean isWarn = (level & WARN) != 0;
boolean isErr = (level & ERR) != 0;
if (full) {
if (isErr) {
buffer.append("*** WARNING *** ");
buffer.append(Messages.bind(Messages.standardError, PERCENT_FORMAT.format(resultStats[1]), STANDARD_ERROR_THRESHOLD_STRING));
}
return buffer.toString();
}
if (resultStats != null) {
double deviation = resultStats[0];
buffer.append("<font color=\"#0000FF\" size=\"1\">");
if (Double.isNaN(deviation) || Double.isInfinite(deviation)) {
buffer.append(" [n/a]");
} else {
double stderr = resultStats[1];
deviation = Math.abs(deviation) < 0.001 ? 0 : -deviation;
if (Double.isNaN(stderr) || Double.isInfinite(stderr)) {
buffer.append(DEVIATION_FORMAT.format(deviation));
buffer.append("</font><font color=\"#DDDD00\" size=\"1\"> ");
buffer.append(" [n/a]");
} else {
buffer.append(DEVIATION_FORMAT.format(deviation));
buffer.append(" [&#177;");
buffer.append(STDERR_FORMAT.format(Math.abs(stderr)));
buffer.append(']');
}
}
buffer.append("</font>");
}
return buffer.toString();
}
/**
* Returns the confidence level for given statistics:
* <ul>
* <li>{@link #NAN}: if the value is infinite or not a number</li>
* <li>{@link #ERR}: if the standard error is over the expected threshold ({@link #STANDARD_ERROR_THRESHOLD})</li>
* <li>{@link #OK}: in all other cases</li>
* </ul>
*
* @param resultStats
* array of 2 doubles, the former is the average value and the latter is the standard error made while computing the
* average.
* @return a value telling caller the level of confidence of the provided value
*/
public static int confidenceLevel(double[] resultStats) {
int level = OK;
if (resultStats != null) {
if (Double.isNaN(resultStats[0]) || Double.isInfinite(resultStats[0])) {
level = NAN;
} else {
// if (resultStats[1] >= (STANDARD_ERROR_THRESHOLD/2)) { //
// warns standard error higher than the half of authorized
// threshold
// level |= WARN;
// }
if (resultStats[1] >= STANDARD_ERROR_THRESHOLD) { // standard
// error
// higher than
// the
// authorized
// threshold
level = ERR;
}
}
}
return level;
}
/**
* Get an icon image corresponding to a given level of confidence and explanation.
*
* @param confidence
* the confiden level
* @param hasExplanation
* flags indicates whether the confidence may be tempered by an explanation
* @return Corresponding image
*/
public static String getImage(int confidence, boolean scenarioFailed, boolean hasExplanation) {
String image = null;
if (scenarioFailed) {
if (hasExplanation) {
image = FAIL_IMAGE_EXPLAINED;
} else if ((confidence & ERR) != 0) {
image = FAIL_IMAGE_WARN;
} else {
image = FAIL_IMAGE;
}
} else if ((confidence & NAN) != 0) {
image = UNKNOWN_IMAGE;
} else if ((confidence & ERR) != 0) {
image = OK_IMAGE_WARN;
} else {
image = OK_IMAGE;
}
return image;
}
/**
* @param outputFile
* @param image
*/
public static void saveImage(File outputFile, Image image) {
// Save image
ImageData data = downSample(image);
ImageLoader imageLoader = new ImageLoader();
imageLoader.data = new ImageData[] { data };
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(outputFile));
imageLoader.save(out, SWT.IMAGE_GIF);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
finally {
image.dispose();
if (out != null) {
try {
out.close();
} catch (IOException e1) {
// silently ignored
}
}
}
}
}