Home | History | Annotate | Download | only in ui
      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.performance.ui;
     12 
     13 import java.io.BufferedOutputStream;
     14 import java.io.File;
     15 import java.io.FileNotFoundException;
     16 import java.io.FileOutputStream;
     17 import java.io.IOException;
     18 import java.io.OutputStream;
     19 import java.net.URL;
     20 import java.text.DecimalFormat;
     21 import java.text.NumberFormat;
     22 import java.util.Arrays;
     23 import java.util.Calendar;
     24 import java.util.Enumeration;
     25 import java.util.HashMap;
     26 
     27 import org.eclipse.swt.SWT;
     28 import org.eclipse.swt.graphics.Image;
     29 import org.eclipse.swt.graphics.ImageData;
     30 import org.eclipse.swt.graphics.ImageLoader;
     31 import org.eclipse.swt.graphics.PaletteData;
     32 import org.eclipse.swt.graphics.RGB;
     33 import org.eclipse.test.internal.performance.PerformanceTestPlugin;
     34 import org.eclipse.test.internal.performance.db.Variations;
     35 import org.eclipse.test.internal.performance.results.utils.Util;
     36 import org.osgi.framework.Bundle;
     37 
     38 
     39 public class Utils {
     40 
     41 	public final static double STANDARD_ERROR_THRESHOLD = 0.03; // 3%
     42 	static final NumberFormat PERCENT_FORMAT = NumberFormat.getPercentInstance();
     43 	static {
     44 		PERCENT_FORMAT.setMaximumFractionDigits(1);
     45 	}
     46 	static final DecimalFormat DEVIATION_FORMAT = (DecimalFormat) NumberFormat.getPercentInstance();
     47 	static {
     48 		DEVIATION_FORMAT.setMaximumFractionDigits(1);
     49 		DEVIATION_FORMAT.setMinimumFractionDigits(1);
     50 		DEVIATION_FORMAT.setPositivePrefix("+");
     51 		DEVIATION_FORMAT.setNegativePrefix("- ");
     52 	}
     53 	static final DecimalFormat STDERR_FORMAT = (DecimalFormat) NumberFormat.getNumberInstance();
     54 	static {
     55 		STDERR_FORMAT.setMaximumFractionDigits(1);
     56 		STDERR_FORMAT.setMinimumFractionDigits(1);
     57 		STDERR_FORMAT.setMultiplier(100);
     58 	}
     59 	public final static String STANDARD_ERROR_THRESHOLD_STRING = PERCENT_FORMAT.format(STANDARD_ERROR_THRESHOLD);
     60 
     61 	// Image files
     62 	public final static String UNKNOWN_IMAGE="images/Unknown.gif";
     63 	public final static String OK_IMAGE="images/OK.gif";
     64 	public final static String OK_IMAGE_WARN="images/OK_caution.gif";
     65 	public final static String FAIL_IMAGE="images/FAIL.gif";
     66 	public final static String FAIL_IMAGE_WARN="images/FAIL_caution.gif";
     67 	public final static String FAIL_IMAGE_EXPLAINED="images/FAIL_greyed.gif";
     68 	public final static String LIGHT="images/light.gif";
     69 	public final static String WARNING_OBJ="images/warning_obj.gif";
     70 
     71 	// Java script files
     72 	public final static String TOOLTIP_SCRIPT = "scripts/ToolTip.js";
     73 	public final static String TOOLTIP_STYLE = "scripts/ToolTip.css";
     74 	public final static String FINGERPRINT_SCRIPT = "scripts/Fingerprints.js";
     75 
     76 	// Doc files
     77 	public final static String HELP = "doc/help.html";
     78 
     79 	// Status
     80 	public final static int OK = 0;
     81 	public final static int NAN = 0x1;
     82 	public final static int ERR = 0x2;
     83 
     84 	/**
     85 	 * Return <html><head><meta http-equiv="Content-Type"
     86 	 *         content="text/html; charset=iso-8859-1">
     87 	 */
     88 	public final static String HTML_OPEN = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n";
     89 
     90 	/**
     91 	 * Return "&lt;/html&gt;".
     92 	 */
     93 	public final static String HTML_CLOSE = "</html>\n";
     94 
     95 	/**
     96 	 * Default style-sheet used on eclipse.org
     97 	 */
     98 	public final static String HTML_DEFAULT_CSS = "<style type=\"text/css\">" + "p, table, td, th {  font-family: arial, helvetica, geneva; font-size: 10pt}\n"
     99 			+ "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"
    100 			+ "code {  font-family: \"Courier New\", Courier, mono; font-size: 10pt}\n" + "sup {  font-family: arial,helvetica,geneva; font-size: 10px}\n"
    101 			+ "h3 {  font-family: arial, helvetica, geneva; font-size: 14pt; font-weight: bold}\n" + "li {  font-family: arial, helvetica, geneva; font-size: 10pt}\n"
    102 			+ "h1 {  font-family: arial, helvetica, geneva; font-size: 28px; font-weight: bold}\n"
    103 			+ "body {  font-family: arial, helvetica, geneva; font-size: 10pt; clip:   rect(   ); margin-top: 5mm; margin-left: 3mm}\n"
    104 			+ ".indextop { font-size: x-large;; font-family: Verdana, Arial, Helvetica, sans-serif; font-weight: bold}\n"
    105 			+ ".indexsub { font-size: xx-small;; font-family: Arial, Helvetica, sans-serif; color: #8080FF}\n" + "</style>\n\n";
    106 
    107 	/**
    108 	 * Creates a Variations object using build id pattern, config and jvm.
    109 	 *
    110 	 * @param buildIdPattern
    111 	 * @param config
    112 	 * @param jvm
    113 	 */
    114 	public static Variations getVariations(String buildIdPattern, String config, String jvm) {
    115 		String buildIdPatterns = buildIdPattern.replace(',', '%');
    116 		Variations variations = new Variations();
    117 		variations.put(PerformanceTestPlugin.CONFIG, config);
    118 		variations.put(PerformanceTestPlugin.BUILD, buildIdPatterns);
    119 		variations.put("jvm", jvm);
    120 		return variations;
    121 	}
    122 
    123 	/**
    124 	 * Copy all bundle files contained in the given path
    125 	 */
    126 	public static void copyBundleFiles(Bundle bundle, String path, String pattern, File output) {
    127 		Enumeration imageFiles = bundle.findEntries(path, pattern, false);
    128 		while (imageFiles.hasMoreElements()) {
    129 			URL url = (URL) imageFiles.nextElement();
    130 			try {
    131 				File outputFile = new File(output, url.getFile());
    132 				if (!outputFile.getParentFile().exists()) {
    133 					outputFile.getParentFile().mkdirs();
    134 				}
    135 				Util.copyStream(url.openStream(), outputFile);
    136 			} catch (IOException e) {
    137 				// TODO Auto-generated catch block
    138 				e.printStackTrace();
    139 			}
    140 		}
    141 	}
    142 
    143 	/**
    144 	 * Downsample Image to 8 bit depth format so that the resulting image data
    145 	 * can be saved to GIF. Note. If the source image contains photo quality
    146 	 * content with more than 256 colours, resulting data will look very poor.
    147 	 */
    148 	static int closest(RGB[] rgbs, int n, RGB rgb) {
    149 		int minDist = 256 * 256 * 3;
    150 		int minIndex = 0;
    151 		for (int i = 0; i < n; ++i) {
    152 			RGB rgb2 = rgbs[i];
    153 			int da = rgb2.red - rgb.red;
    154 			int dg = rgb2.green - rgb.green;
    155 			int db = rgb2.blue - rgb.blue;
    156 			int dist = da * da + dg * dg + db * db;
    157 			if (dist < minDist) {
    158 				minDist = dist;
    159 				minIndex = i;
    160 			}
    161 		}
    162 		return minIndex;
    163 	}
    164 
    165 	static class ColorCounter implements Comparable {
    166 		RGB rgb;
    167 
    168 		int count;
    169 
    170 		public int compareTo(Object o) {
    171 			return ((ColorCounter) o).count - this.count;
    172 		}
    173 	}
    174 
    175 	public static ImageData downSample(Image image) {
    176 		ImageData data = image.getImageData();
    177 		if (!data.palette.isDirect && data.depth <= 8)
    178 			return data;
    179 
    180 		// compute a histogram of color frequencies
    181 		HashMap freq = new HashMap();
    182 		int width = data.width;
    183 		int[] pixels = new int[width];
    184 		int[] maskPixels = new int[width];
    185 		for (int y = 0, height = data.height; y < height; ++y) {
    186 			data.getPixels(0, y, width, pixels, 0);
    187 			for (int x = 0; x < width; ++x) {
    188 				RGB rgb = data.palette.getRGB(pixels[x]);
    189 				ColorCounter counter = (ColorCounter) freq.get(rgb);
    190 				if (counter == null) {
    191 					counter = new ColorCounter();
    192 					counter.rgb = rgb;
    193 					freq.put(rgb, counter);
    194 				}
    195 				counter.count++;
    196 			}
    197 		}
    198 
    199 		// sort colors by most frequently used
    200 		ColorCounter[] counters = new ColorCounter[freq.size()];
    201 		freq.values().toArray(counters);
    202 		Arrays.sort(counters);
    203 
    204 		// pick the most frequently used 256 (or fewer), and make a palette
    205 		ImageData mask = null;
    206 		if (data.transparentPixel != -1 || data.maskData != null) {
    207 			mask = data.getTransparencyMask();
    208 		}
    209 		int n = Math.min(256, freq.size());
    210 		RGB[] rgbs = new RGB[n + (mask != null ? 1 : 0)];
    211 		for (int i = 0; i < n; ++i)
    212 			rgbs[i] = counters[i].rgb;
    213 		if (mask != null) {
    214 			rgbs[rgbs.length - 1] = data.transparentPixel != -1 ? data.palette.getRGB(data.transparentPixel) : new RGB(255, 255, 255);
    215 		}
    216 		PaletteData palette = new PaletteData(rgbs);
    217 
    218 		// create a new image using the new palette:
    219 		// for each pixel in the old image, look up the best matching
    220 		// index in the new palette
    221 		ImageData newData = new ImageData(width, data.height, 8, palette);
    222 		if (mask != null)
    223 			newData.transparentPixel = rgbs.length - 1;
    224 		for (int y = 0, height = data.height; y < height; ++y) {
    225 			data.getPixels(0, y, width, pixels, 0);
    226 			if (mask != null)
    227 				mask.getPixels(0, y, width, maskPixels, 0);
    228 			for (int x = 0; x < width; ++x) {
    229 				if (mask != null && maskPixels[x] == 0) {
    230 					pixels[x] = rgbs.length - 1;
    231 				} else {
    232 					RGB rgb = data.palette.getRGB(pixels[x]);
    233 					pixels[x] = closest(rgbs, n, rgb);
    234 				}
    235 			}
    236 			newData.setPixels(0, y, width, pixels, 0);
    237 		}
    238 		return newData;
    239 	}
    240 
    241 	/**
    242 	 * Returns the date/time from the build id in format yyyymmddhm
    243 	 *
    244 	 * @param buildId
    245 	 * @return date/time in format YYYYMMDDHHMM, ie. 200504060010
    246 	 */
    247 	public static long getDateFromBuildID(String buildId) {
    248 		return getDateFromBuildID(buildId, false);
    249 	}
    250 
    251 	public static long getDateFromBuildID(String buildId, boolean matchLast) {
    252 		Calendar calendar = Calendar.getInstance();
    253 
    254 		if (buildId.indexOf('_') != -1) {
    255 			String[] buildIdParts = buildId.split("_");
    256 
    257 			int buildIdSegment = 1;
    258 			if (matchLast)
    259 				buildIdSegment = buildIdParts.length - 1;
    260 			// if release build, expect <release>_<release date and
    261 			// timestamp>_<date and timestamp test ran>
    262 			// use test date and time for plotting
    263 			int year = Integer.parseInt(buildIdParts[buildIdSegment].substring(0, 4));
    264 			int month = Integer.parseInt(buildIdParts[buildIdSegment].substring(4, 6)) - 1;
    265 			int date = Integer.parseInt(buildIdParts[buildIdSegment].substring(6, 8));
    266 			int hours = Integer.parseInt(buildIdParts[buildIdSegment].substring(8, 10));
    267 			int min = Integer.parseInt(buildIdParts[buildIdSegment].substring(10, 12));
    268 
    269 			calendar.set(year, month, date, hours, min);
    270 			return calendar.getTimeInMillis();
    271 
    272 		} else if (buildId.indexOf('-') != -1) {
    273 			// if regular build, expect <buildType><date>-<time> format
    274 			String[] buildIdParts = buildId.split("-");
    275 			int year = Integer.parseInt(buildIdParts[0].substring(1, 5));
    276 			int month = Integer.parseInt(buildIdParts[0].substring(5, 7)) - 1;
    277 			int date = Integer.parseInt(buildIdParts[0].substring(7, 9));
    278 			int hours = Integer.parseInt(buildIdParts[1].substring(0, 2));
    279 			int min = Integer.parseInt(buildIdParts[1].substring(2, 4));
    280 			calendar.set(year, month, date, hours, min);
    281 
    282 			return calendar.getTimeInMillis();
    283 		}
    284 
    285 		return -1;
    286 	}
    287 
    288 	/**
    289 	 * Returns a message corresponding to given statistics.
    290 	 *
    291 	 * @param resultStats The value with its standard error
    292 	 * @param full
    293 	 * @return The failure message. May be empty if stats are good...
    294 	 */
    295 	public static String failureMessage(double[] resultStats, boolean full) {
    296 		StringBuffer buffer = new StringBuffer();
    297 		int level = confidenceLevel(resultStats);
    298 //		boolean isWarn = (level & WARN) != 0;
    299 		boolean isErr = (level & ERR) != 0;
    300 		if (full) {
    301 			if (isErr) {
    302 				buffer.append("*** WARNING ***  ");
    303 	 			buffer.append(Messages.bind(Messages.standardError, PERCENT_FORMAT.format(resultStats[1]), STANDARD_ERROR_THRESHOLD_STRING));
    304 			}
    305 			return buffer.toString();
    306 		}
    307 		if (resultStats != null) {
    308 			double deviation = resultStats[0];
    309 			buffer.append("<font color=\"#0000FF\" size=\"1\">");
    310 			if (Double.isNaN(deviation) || Double.isInfinite(deviation)) {
    311 	 			buffer.append(" [n/a]");
    312  			} else {
    313 				double stderr = resultStats[1];
    314 				deviation = Math.abs(deviation)<0.001 ? 0 : -deviation;
    315 	 			if (Double.isNaN(stderr) || Double.isInfinite(stderr)) {
    316 		 			buffer.append(DEVIATION_FORMAT.format(deviation));
    317 					buffer.append("</font><font color=\"#DDDD00\" size=\"1\"> ");
    318 		 			buffer.append(" [n/a]");
    319 	 			} else {
    320 		 			buffer.append(DEVIATION_FORMAT.format(deviation));
    321 	 				buffer.append(" [&#177;");
    322 	 				buffer.append(STDERR_FORMAT.format(Math.abs(stderr)));
    323 	 				buffer.append(']');
    324 	 			}
    325  			}
    326 			buffer.append("</font>");
    327 		}
    328 		return buffer.toString();
    329 	}
    330 
    331 	/**
    332 	 * Returns the confidence level for given statistics:
    333 	 * <ul>
    334 	 * <li>{@link #NAN}: if the value is infinite or not a number</li>
    335 	 * <li>{@link #ERR}: if the standard error is over the expected threshold ({@link #STANDARD_ERROR_THRESHOLD})</li>
    336 	 * <li>{@link #OK}: in all other cases</li>
    337 	 * </ul>
    338 	 *
    339 	 * @param resultStats array of 2 doubles, the former is the average value and
    340 	 * 	the latter is the standard error made while computing the average.
    341 	 * @return a value telling caller the level of confidence of the provided value
    342 	 */
    343 	public static int confidenceLevel(double[] resultStats) {
    344 		int level = OK;
    345  		if (resultStats != null){
    346 			if (Double.isNaN(resultStats[0]) || Double.isInfinite(resultStats[0])) {
    347 				level = NAN;
    348  			} else {
    349 //	 			if (resultStats[1] >= (STANDARD_ERROR_THRESHOLD/2)) { // warns standard error higher than the half of authorized threshold
    350 //	 				level |= WARN;
    351 //	 			}
    352 	 			if (resultStats[1] >= STANDARD_ERROR_THRESHOLD) { // standard error higher than the authorized threshold
    353 	 				level = ERR;
    354 	 			}
    355  			}
    356  		}
    357 		return level;
    358 	}
    359 
    360 	/**
    361 	 * Get an icon image corresponding to a given level of confidence and explanation.
    362 	 *
    363 	 * @param confidence the confiden level
    364 	 * @param hasExplanation flags indicates whether the confidence may be tempered by an explanation
    365 	 * @return Corresponding image
    366 	 */
    367 	public static String getImage(int confidence, boolean scenarioFailed, boolean hasExplanation) {
    368 	    String image = null;
    369 
    370 	    if (scenarioFailed) {
    371 	    	if (hasExplanation) {
    372 		    	image = FAIL_IMAGE_EXPLAINED;
    373 		    } else if ((confidence & ERR) != 0) {
    374     			image = FAIL_IMAGE_WARN;
    375 		    } else {
    376     			image = FAIL_IMAGE;
    377 		    }
    378 	    } else if ((confidence & NAN) != 0) {
    379 			image = UNKNOWN_IMAGE;
    380 	    } else if ((confidence & ERR) != 0) {
    381 	   		image = OK_IMAGE_WARN;
    382 	    } else {
    383    			image = OK_IMAGE;
    384 	    }
    385 	    return image;
    386     }
    387 
    388 /**
    389  * @param outputFile
    390  * @param image
    391  */
    392 public static void saveImage(File outputFile, Image image) {
    393 	// Save image
    394 	ImageData data = downSample(image);
    395 	ImageLoader imageLoader = new ImageLoader();
    396 	imageLoader.data = new ImageData[] { data };
    397 
    398 	OutputStream out = null;
    399 	try {
    400 		out = new BufferedOutputStream(new FileOutputStream(outputFile));
    401 		imageLoader.save(out, SWT.IMAGE_GIF);
    402 	} catch (FileNotFoundException e) {
    403 		e.printStackTrace();
    404 	} finally {
    405 		image.dispose();
    406 		if (out != null) {
    407 			try {
    408 				out.close();
    409 			} catch (IOException e1) {
    410 				// silently ignored
    411 			}
    412 		}
    413 	}
    414 }
    415 
    416 }