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 "&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 */
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}