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 "</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 */
getVariations(String buildIdPattern, String config, String jvm)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 */
copyBundleFiles(Bundle bundle, String path, String pattern, File output)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 */
closest(RGB[] rgbs, int n, RGB rgb)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
compareTo(Object o)170 public int compareTo(Object o) {
171 return ((ColorCounter) o).count - this.count;
172 }
173 }
174
downSample(Image image)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 */
getDateFromBuildID(String buildId)247 public static long getDateFromBuildID(String buildId) {
248 return getDateFromBuildID(buildId, false);
249 }
250
getDateFromBuildID(String buildId, boolean matchLast)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 */
failureMessage(double[] resultStats, boolean full)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 */
getImage(int confidence, boolean scenarioFailed, boolean hasExplanation)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 */
saveImage(File outputFile, Image image)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 }