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 *******************************************************************************/ 11package org.eclipse.test.performance.ui; 12 13import java.io.BufferedOutputStream; 14import java.io.File; 15import java.io.FileNotFoundException; 16import java.io.FileOutputStream; 17import java.io.IOException; 18import java.io.OutputStream; 19import java.net.URL; 20import java.text.DecimalFormat; 21import java.text.NumberFormat; 22import java.util.Arrays; 23import java.util.Calendar; 24import java.util.Enumeration; 25import java.util.HashMap; 26 27import org.eclipse.swt.SWT; 28import org.eclipse.swt.graphics.Image; 29import org.eclipse.swt.graphics.ImageData; 30import org.eclipse.swt.graphics.ImageLoader; 31import org.eclipse.swt.graphics.PaletteData; 32import org.eclipse.swt.graphics.RGB; 33import org.eclipse.test.internal.performance.PerformanceTestPlugin; 34import org.eclipse.test.internal.performance.db.Variations; 35import org.eclipse.test.internal.performance.results.utils.Util; 36import org.osgi.framework.Bundle; 37 38 39public 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 "</html>". 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(" [±"); 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 */ 392public 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}