package com.dubbo.serialize.benchmark; import java.io.*; import java.net.URLEncoder; import java.util.*; import java.util.regex.Pattern; import java.util.zip.DeflaterOutputStream; import serializers.jackson.*; import serializers.json.JsonGsonDatabind; import serializers.json.JsonArgoTree; import serializers.json.FastJSONDatabind; import serializers.json.FlexjsonDatabind; import serializers.json.JsonGsonManual; import serializers.json.JsonGsonTree; import serializers.json.JsonDotOrgManualTree; import serializers.json.JsonLibJsonDatabind; import serializers.json.JsonPathDeserializerOnly; import serializers.json.JsonSimpleManualTree; import serializers.json.JsonSimpleWithContentHandler; import serializers.json.JsonSmartManualTree; import serializers.json.JsonTwoLattes; import serializers.json.JsonijJpath; import serializers.json.JsonijManualTree; import serializers.json.JsonSvensonDatabind; import serializers.protostuff.Protostuff; import serializers.protostuff.ProtostuffJson; import serializers.protostuff.ProtostuffSmile; import serializers.xml.XmlJavolution; import serializers.xml.XmlStax; import serializers.xml.XmlXStream; public class BenchmarkRunner { public final static int DEFAULT_ITERATIONS = 2000; public final static int DEFAULT_TRIALS = 20; /** * Number of milliseconds to warm up for each operation type for each serializer. Let's * start with 3 seconds. */ final static long DEFAULT_WARMUP_MSECS = 3000; // These tests aren't included by default. Use the "-hidden" flag to enable them. private static final HashSet<String> HIDDEN = new HashSet<String>(); static { // CKS is not included because it's not really publicly released. HIDDEN.add("cks"); HIDDEN.add("cks-text"); } private static final String ERROR_DIVIDER = "-------------------------------------------------------------------"; public static void main(String[] args) { // -------------------------------------------------- // Parse command-line options. Boolean filterIsInclude = null; Set<String> filterStrings = null; Integer iterations = null; Integer trials = null; Long warmupTime = null; boolean printChart = false; boolean prewarm = false; String dataFileName = null; boolean enableHidden = false; Set<String> optionsSeen = new HashSet<String>(); for (String arg : args) { String remainder; if (arg.startsWith("--")) { remainder = arg.substring(2); } else if (arg.startsWith("-")) { remainder = arg.substring(1); } else if (dataFileName == null) { dataFileName = arg; continue; } else { System.err.println("Expecting only one non-option argument (<data-file> = \"" + dataFileName + "\")."); System.err.println("Found a second one: \"" + arg + "\""); System.err.println("Use \"-help\" for usage information."); System.exit(1); return; } String option, value; int eqPos = remainder.indexOf('='); if (eqPos >= 0) { option = remainder.substring(0, eqPos); value = remainder.substring(eqPos+1); } else { option = remainder; value = null; } if (!optionsSeen.add(option)) { System.err.println("Repeated option: \"" + arg + "\""); System.exit(1); return; } if (option.equals("include")) { if (value == null) { System.err.println("The \"include\" option requires a value."); System.exit(1); return; } if (filterIsInclude == null) { filterIsInclude = true; filterStrings = new HashSet<String>(Arrays.asList(value.split(","))); } else { System.err.println("Can't use 'include' and 'exclude' options at the same time."); System.exit(1); return; } } else if (option.equals("exclude")) { if (value == null) { System.err.println("The \"exclude\" option requires a value."); System.exit(1); return; } if (filterIsInclude == null) { filterIsInclude = false; filterStrings = new HashSet<String>(Arrays.asList(value.split(","))); } else { System.err.println("Can't use 'include' and 'exclude' options at the same time."); System.exit(1); return; } } else if (option.equals("iterations")) { if (value == null) { System.err.println("The \"iterations\" option requires a value."); System.exit(1); return; } assert iterations == null; try { iterations = Integer.parseInt(value); } catch (NumberFormatException ex) { System.err.println("Invalid value for \"iterations\" option: \"" + value + "\""); System.exit(1); return; } if (iterations < 1) { System.err.println("Invalid value for \"iterations\" option: \"" + value + "\""); System.exit(1); return; } } else if (option.equals("trials")) { if (value == null) { System.err.println("The \"trials\" option requires a value."); System.exit(1); return; } assert trials == null; try { trials = Integer.parseInt(value); } catch (NumberFormatException ex) { System.err.println("Invalid value for \"trials\" option: \"" + value + "\""); System.exit(1); return; } if (trials < 1) { System.err.println("Invalid value for \"trials\" option: \"" + value + "\""); System.exit(1); return; } } else if (option.equals("warmup-time")) { if (value == null) { System.err.println("The \"warmup-time\" option requires a value."); System.exit(1); return; } assert warmupTime == null; try { warmupTime = Long.parseLong(value); } catch (NumberFormatException ex) { System.err.println("Invalid value for \"warmup-time\" option: \"" + value + "\""); System.exit(1); return; } if (warmupTime < 0) { System.err.println("Invalid value for \"warmup-time\" option: \"" + value + "\""); System.exit(1); return; } } else if (option.equals("pre-warmup")) { if (value != null) { System.err.println("The \"pre-warmup\" option does not take a value: \"" + arg + "\""); System.exit(1); return; } assert !prewarm; prewarm = true; } else if (option.equals("chart")) { if (value != null) { System.err.println("The \"chart\" option does not take a value: \"" + arg + "\""); System.exit(1); return; } assert !printChart; printChart = true; } else if (option.equals("hidden")) { if (value != null) { System.err.println("The \"hidden\" option does not take a value: \"" + arg + "\""); System.exit(1); return; } assert !enableHidden; enableHidden = true; } else if (option.equals("help")) { if (value != null) { System.err.println("The \"help\" option does not take a value: \"" + arg + "\""); System.exit(1); return; } if (args.length != 1) { System.err.println("The \"help\" option cannot be combined with any other option."); System.exit(1); return; } System.out.println(); System.out.println("Usage: run [options] <data-file>"); System.out.println(); System.out.println("Options:"); System.out.println(" -iterations=n [default=" + DEFAULT_ITERATIONS + "]"); System.out.println(" -trials=n [default=" + DEFAULT_TRIALS + "]"); System.out.println(" -warmup-time=millis [default=" + DEFAULT_WARMUP_MSECS + "]"); System.out.println(" -pre-warmup (warm all serializers before the first measurement)"); System.out.println(" -chart (generate a Google Chart URL for the results)"); System.out.println(" -include=impl1,impl2,impl3,..."); System.out.println(" -exclude=impl1,impl2,impl3,..."); System.out.println(" -hidden (enable \"hidden\" serializers)"); System.out.println(" -help"); System.out.println(); System.out.println("Example: run -chart -include=protobuf,thrift data/media.1.cks"); System.out.println(); System.exit(0); return; } else { System.err.println("Unknown option: \"" + arg + "\""); System.err.println("Use \"-help\" for usage information."); System.exit(1); return; } } if (iterations == null) iterations = DEFAULT_ITERATIONS; if (trials == null) trials = DEFAULT_TRIALS; if (warmupTime == null) warmupTime = DEFAULT_WARMUP_MSECS; if (dataFileName == null) { System.err.println("Missing <data-file> argument."); System.err.println("Use \"-help\" for usage information."); System.exit(1); return; } // -------------------------------------------------- // Load serializers. TestGroups groups = new TestGroups(); // Binary Formats; language-specific ones JavaBuiltIn.register(groups); JavaManual.register(groups); Scala.register(groups); // hessian and kryo are Java object serializations Hessian.register(groups); Dubbo.register(groups); Kryo.register(groups); Wobly.register(groups); // Binary formats, generic: protobuf, thrift, avro, kryo, CKS, msgpack Protobuf.register(groups); ActiveMQProtobuf.register(groups); Protostuff.register(groups); Thrift.register(groups); AvroSpecific.register(groups); AvroGeneric.register(groups); CksBinary.register(groups); MsgPack.register(groups); // JSON JacksonJsonManual.register(groups); JacksonJsonTree.register(groups); JacksonJsonTreeWithStrings.register(groups); JacksonJsonDatabind.register(groups); JacksonJsonDatabindWithStrings.register(groups); JsonTwoLattes.register(groups); ProtostuffJson.register(groups); ProtobufJson.register(groups); JsonGsonManual.register(groups); JsonGsonTree.register(groups); JsonGsonDatabind.register(groups); JsonSvensonDatabind.register(groups); FlexjsonDatabind.register(groups); JsonLibJsonDatabind.register(groups); FastJSONDatabind.register(groups); JsonSimpleWithContentHandler.register(groups); JsonSimpleManualTree.register(groups); JsonSmartManualTree.register(groups); JsonDotOrgManualTree.register(groups); JsonijJpath.register(groups); JsonijManualTree.register(groups); JsonArgoTree.register(groups); JsonPathDeserializerOnly.register(groups); // Then JSON-like // CKS text is textual JSON-like format CksText.register(groups); // then binary variants // BSON is binary JSON-like format JacksonBsonManual.register(groups); JacksonBsonDatabind.register(groups); MongoDB.register(groups); // Smile is 1-to-1 binary representation of JSON JacksonSmileManual.register(groups); JacksonSmileDatabind.register(groups); ProtostuffSmile.register(groups); // XML-based formats. XmlStax.register(groups); XmlXStream.register(groups); JacksonXmlDatabind.register(groups); XmlJavolution.register(groups); // -------------------------------------------------- // Load data value. Object dataValue; TestGroup<?> group; { File dataFile = new File(dataFileName); if (!dataFile.exists()) { System.out.println("Couldn't find data file \"" + dataFile.getPath() + "\""); System.exit(1); return; } String[] parts = dataFile.getName().split("\\."); if (parts.length < 3) { System.out.println("Data file \"" + dataFile.getName() + "\" should be of the form \"<type>.<name>.<extension>\""); System.exit(1); return; } String dataType = parts[0]; String extension = parts[parts.length-1]; group = groups.groupMap.get(dataType); if (group == null) { System.out.println("Data file \"" + dataFileName + "\" can't be loaded."); System.out.println("Don't know about data type \"" + dataType + "\""); System.exit(1); return; } TestGroup.Entry<?,Object> loader = group.extensionMap.get(parts[parts.length-1]); if (loader == null) { System.out.println("Data file \"" + dataFileName + "\" can't be loaded."); System.out.println("No deserializer registered for data type \"" + dataType + "\" and file extension \"." + extension + "\""); System.exit(1); return; } Object deserialized; try { byte[] fileBytes = readFile(new File(dataFileName)); // Load entire file into a byte array. deserialized = loader.serializer.deserialize(fileBytes); } catch (Exception ex) { System.err.println("Error loading data from file \"" + dataFileName + "\"."); System.err.println(ex.getMessage()); System.exit(1); return; } dataValue = loader.transformer.reverse(deserialized); } @SuppressWarnings("unchecked") TestGroup<Object> group_ = (TestGroup<Object>) group; // -------------------------------------------------- Set<String> matched = new HashSet<String>(); Iterable<TestGroup.Entry<Object,Object>> available; if (enableHidden) { // Use all of them. available = group_.entries; } else { // Remove the hidden ones. ArrayList<TestGroup.Entry<Object,Object>> unhidden = new ArrayList<TestGroup.Entry<Object,Object>>(); for (TestGroup.Entry<?,Object> entry_ : group.entries) { @SuppressWarnings("unchecked") TestGroup.Entry<Object,Object> entry = (TestGroup.Entry<Object,Object>) entry_; String name = entry.serializer.getName(); if (!HIDDEN.contains(name)) unhidden.add(entry); } available = unhidden; } Iterable<TestGroup.Entry<Object,Object>> matchingEntries; if (filterStrings == null) { matchingEntries = available; } else { ArrayList<TestGroup.Entry<Object,Object>> al = new ArrayList<TestGroup.Entry<Object,Object>>(); matchingEntries = al; for (TestGroup.Entry<?,Object> entry_ : available) { @SuppressWarnings("unchecked") TestGroup.Entry<Object,Object> entry = (TestGroup.Entry<Object,Object>) entry_; String name = entry.serializer.getName(); // See if any of the filters match. boolean found = false; for (String s : filterStrings) { boolean thisOneMatches = match(s, name); if (thisOneMatches) { matched.add(s); found = true; } } if (found == filterIsInclude) { al.add(entry); } } Set<String> unmatched = new HashSet<String>(filterStrings); unmatched.removeAll(matched); for (String s : unmatched) { System.err.println("Warning: there is no implementation name matching the pattern \"" + s + "\""); if (!enableHidden) { for (String hiddenName : HIDDEN) { if (match(s, hiddenName)) { System.err.println("(The \"" + hiddenName + "\", serializer is hidden by default."); System.err.println(" Use the \"-hidden\" option to enable hidden serializers)"); break; } } } } } EnumMap<measurements, Map<String, Double>> values; StringWriter errors = new StringWriter(); PrintWriter errorsPW = new PrintWriter(errors); try { values = start(errorsPW, iterations, trials, warmupTime, prewarm, matchingEntries, dataValue); } catch (Exception ex) { ex.printStackTrace(System.err); System.exit(1); return; } if (printChart) { printImages(values); } // Print errors after chart. That way you can't miss it. String errorsString = errors.toString(); if (errorsString.length() > 0) { System.out.println(ERROR_DIVIDER); System.out.println("Errors occurred during benchmarking:"); System.out.print(errorsString); System.exit(1); return; } } private static boolean match(String pattern, String name) { StringBuilder regex = new StringBuilder(); while (pattern.length() > 0) { int starPos = pattern.indexOf('*'); if (starPos < 0) { regex.append(Pattern.quote(pattern)); break; } else { String beforeStar = pattern.substring(0, starPos); String afterStar = pattern.substring(starPos + 1); regex.append(Pattern.quote(beforeStar)); regex.append(".*"); pattern = afterStar; } } return Pattern.matches(regex.toString(), name); } // ------------------------------------------------------------------------------------ private static byte[] readFile(File file) throws IOException { FileInputStream fin = new FileInputStream(file); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); byte[] data = new byte[1024]; while (true) { int numBytes = fin.read(data); if (numBytes < 0) break; baos.write(data, 0, numBytes); } return baos.toByteArray(); } finally { fin.close(); } } // ------------------------------------------------------------------------------------ private static double iterationTime(long delta, int iterations) { return (double) delta / (double) (iterations); } private static final TestCase Create = new TestCase() { public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception { long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { transformer.forward(value); } return iterationTime(System.nanoTime() - start, iterations); } }; private static final TestCase Serialize = new TestCase() { public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception { long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { Object obj = transformer.forward(value); serializer.serialize(obj); } return iterationTime(System.nanoTime() - start, iterations); } }; private static final TestCase SerializeSameObject = new TestCase() { public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception { // let's reuse same instance to reduce overhead Object obj = transformer.forward(value); long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { serializer.serialize(obj); //if (i % 1000 == 0) // doGc(); } return iterationTime(System.nanoTime() - start, iterations); } }; private static final TestCase Deserialize = new TestCase() { public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception { byte[] array = serializer.serialize(transformer.forward(value)); long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { serializer.deserialize(array); } return iterationTime(System.nanoTime() - start, iterations); } }; private static final TestCase DeserializeAndCheck = new TestCase() { public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception { byte[] array = serializer.serialize(transformer.forward(value)); long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { Object obj = serializer.deserialize(array); transformer.reverse(obj); } return iterationTime(System.nanoTime() - start, iterations); } }; private static final TestCase DeserializeAndCheckShallow = new TestCase() { public <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception { byte[] array = serializer.serialize(transformer.forward(value)); long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { Object obj = serializer.deserialize(array); transformer.shallowReverse(obj); } return iterationTime(System.nanoTime() - start, iterations); } }; /** * JVM is not required to honor GC requests, but adding bit of sleep around request is * most likely to give it a chance to do it. */ private static void doGc() { try { Thread.sleep(50L); } catch (InterruptedException ie) { System.err.println("Interrupted while sleeping in serializers.BenchmarkRunner.doGc()"); } System.gc(); try { // longer sleep afterwards (not needed by GC, but may help with scheduling) Thread.sleep(200L); } catch (InterruptedException ie) { System.err.println("Interrupted while sleeping in serializers.BenchmarkRunner.doGc()"); } } // ------------------------------------------------------------------------------------ private static abstract class TestCase { public abstract <J> double run(Transformer<J,Object> transformer, Serializer<Object> serializer, J value, int iterations) throws Exception; } private static final class TestCaseRunner<J> { private final Transformer<J,Object> transformer; private final Serializer<Object> serializer; private final J value; public TestCaseRunner(Transformer<J,Object> transformer, Serializer<Object> serializer, J value) { this.transformer = transformer; this.serializer = serializer; this.value = value; } public double run(TestCase tc, int iterations) throws Exception { return tc.run(transformer, serializer, value, iterations); } public double runTakeMin(int trials, TestCase tc, int iterations) throws Exception { double minTime = Double.MAX_VALUE; for (int i = 0; i < trials; i++) { double time = tc.run(transformer, serializer, value, iterations); minTime = Math.min(minTime, time); } return minTime; } } enum measurements { timeCreate("create (nanos)"), timeSerializeDifferentObjects("ser (nanos)"), timeSerializeSameObject("ser+same (nanos)"), timeDeserializeNoFieldAccess("deser (nanos)"), timeDeserializeAndCheck("deser+deep (nanos)"), timeDeserializeAndCheckShallow("deser+shal (nanos)"), totalTime("total (nanos)"), length("size (bytes)"), lengthDeflate("size+dfl (bytes)"), ; public final String displayName; measurements(String displayName) { this.displayName = displayName; } } private static <J> EnumMap<measurements, Map<String, Double>> start(PrintWriter errors, int iterations, int trials, long warmupTime, boolean prewarm, Iterable<TestGroup.Entry<J,Object>> groups, J value) throws Exception { // Check correctness first. System.out.println("Checking correctness..."); for (TestGroup.Entry<J,Object> entry : groups) { checkCorrectness(errors, entry.transformer, entry.serializer, value); } System.out.println("[done]"); // Pre-warm. if (prewarm) { System.out.print("Pre-warmup..."); for (TestGroup.Entry<J,Object> entry : groups) { TestCaseRunner<J> runner = new TestCaseRunner<J>(entry.transformer, entry.serializer, value); String name = entry.serializer.getName(); System.out.print(" " + name); warmCreation(runner, warmupTime); warmSerialization(runner, warmupTime); warmDeserialization(runner, warmupTime); } System.out.println(); System.out.println("[done]"); } System.out.printf("%-32s %6s %7s %7s %7s %7s %7s %7s %6s %5s\n", "", "create", "ser", "+same", "deser", "+shal", "+deep", "total", "size", "+dfl"); EnumMap<measurements, Map<String, Double>> values = new EnumMap<measurements, Map<String, Double>>(measurements.class); for (measurements m : measurements.values()) values.put(m, new HashMap<String, Double>()); // Actual tests. for (TestGroup.Entry<J,Object> entry : groups) { TestCaseRunner<J> runner = new TestCaseRunner<J>(entry.transformer, entry.serializer, value); String name = entry.serializer.getName(); try { /* * Should only warm things for the serializer that we test next: HotSpot JIT will * otherwise spent most of its time optimizing slower ones... Use * -XX:CompileThreshold=1 to hint the JIT to start immediately * * Actually: 1 is often not a good value -- threshold is the number * of samples needed to trigger inlining, and there's no point in * inlining everything. Default value is in thousands, so lowering * it to, say, 1000 is usually better. */ warmCreation(runner, warmupTime); doGc(); double timeCreate = runner.runTakeMin(trials, Create, iterations * 100); // do more iteration for object creation because of its short time warmSerialization(runner, warmupTime); doGc(); double timeSerializeDifferentObjects = runner.runTakeMin(trials, Serialize, iterations); doGc(); double timeSerializeSameObject = runner.runTakeMin(trials, SerializeSameObject, iterations); warmDeserialization(runner, warmupTime); doGc(); double timeDeserializeNoFieldAccess = runner.runTakeMin(trials, Deserialize, iterations); doGc(); double timeDeserializeAndCheckShallow = runner.runTakeMin(trials, DeserializeAndCheckShallow, iterations); doGc(); double timeDeserializeAndCheck = runner.runTakeMin(trials, DeserializeAndCheck, iterations); double totalTime = timeSerializeDifferentObjects + timeDeserializeAndCheck; byte[] array = entry.serializer.serialize(entry.transformer.forward(value)); byte[] compressDeflate = compressDeflate(array); System.out.printf("%-32s %6.0f %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f %6d %5d\n", name, timeCreate, timeSerializeDifferentObjects, timeSerializeSameObject, timeDeserializeNoFieldAccess, timeDeserializeAndCheckShallow, timeDeserializeAndCheck, totalTime, array.length, compressDeflate.length); addValue(values, name, timeCreate, timeSerializeDifferentObjects, timeSerializeSameObject, timeDeserializeNoFieldAccess, timeDeserializeAndCheckShallow, timeDeserializeAndCheck, totalTime, array.length, compressDeflate.length); } catch (Exception ex) { System.out.println("ERROR: \"" + name + "\" crashed during benchmarking."); errors.println(ERROR_DIVIDER); errors.println("\"" + name + "\" crashed during benchmarking."); ex.printStackTrace(errors); } } return values; } private static byte[] compressDeflate(byte[] data) { try { ByteArrayOutputStream bout = new ByteArrayOutputStream(500); DeflaterOutputStream compresser = new DeflaterOutputStream(bout); compresser.write(data, 0, data.length); compresser.finish(); compresser.flush(); return bout.toByteArray(); } catch (IOException ex) { AssertionError ae = new AssertionError("IOException while writing to ByteArrayOutputStream!"); ae.initCause(ex); throw ae; } } /** * Method that tries to validate correctness of serializer, using * round-trip (construct, serializer, deserialize; compare objects * after steps 1 and 3). */ private static <J> void checkCorrectness(PrintWriter errors, Transformer<J,Object> transformer, Serializer<Object> serializer, J value) throws Exception { Object specialInput; String name = serializer.getName(); try { specialInput = transformer.forward(value); } catch (Exception ex) { System.out.println("ERROR: \"" + name + "\" crashed during forward transformation."); errors.println(ERROR_DIVIDER); errors.println("\"" + name + "\" crashed during forward transformation."); ex.printStackTrace(errors); return; } byte[] array; try { array = serializer.serialize(specialInput); } catch (Exception ex) { System.out.println("ERROR: \"" + name + "\" crashed during serialization."); errors.println(ERROR_DIVIDER); errors.println("\"" + name + "\" crashed during serialization."); ex.printStackTrace(errors); return; } Object specialOutput; try { specialOutput = serializer.deserialize(array); } catch (Exception ex) { System.out.println("ERROR: \"" + name + "\" crashed during deserialization."); errors.println(ERROR_DIVIDER); errors.println("\"" + name + "\" crashed during deserialization."); ex.printStackTrace(errors); return; } J output; try { output = transformer.reverse(specialOutput); } catch (Exception ex) { System.out.println("ERROR: \"" + name + "\" crashed during reverse transformation."); errors.println(ERROR_DIVIDER); errors.println("\"" + name + "\" crashed during reverse transformation."); ex.printStackTrace(errors); return; } if (!value.equals(output)) { System.out.println("ERROR: \"" + name + "\" failed round-trip check."); errors.println(ERROR_DIVIDER); errors.println("\"" + name + "\" failed round-trip check."); errors.println("ORIGINAL: " + value); errors.println("ROUNDTRIP: " + output); } } private static void printImages(EnumMap<measurements, Map<String, Double>> values) { for (measurements m : values.keySet()) { Map<String, Double> map = values.get(m); ArrayList<Map.Entry<String,Double>> list = new ArrayList<Map.Entry<String,Double>>(map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String,Double>>() { public int compare (Map.Entry<String,Double> o1, Map.Entry<String,Double> o2) { double diff = o1.getValue() - o2.getValue(); return diff > 0 ? 1 : (diff < 0 ? -1 : 0); } }); LinkedHashMap<String, Double> sortedMap = new LinkedHashMap<String, Double>(); for (Map.Entry<String, Double> entry : list) { if( !entry.getValue().isNaN() ) { sortedMap.put(entry.getKey(), entry.getValue()); } } if (!sortedMap.isEmpty()) printImage(sortedMap, m); } } private static String urlEncode(String s) { try { return URLEncoder.encode(s, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private static void printImage(Map<String, Double> map, measurements m) { StringBuilder valSb = new StringBuilder(); String names = ""; double max = Double.MIN_NORMAL; for (Map.Entry<String, Double> entry : map.entrySet()) { double value = entry.getValue(); valSb.append((int) value).append(','); max = Math.max(max, entry.getValue()); names = urlEncode(entry.getKey()) + '|' + names; } int headerSize = 30; int maxPixels = 300 * 1000; // Limit set by Google's Chart API. int maxHeight = 600; int width = maxPixels / maxHeight; int barThickness = 10; int barSpacing = 10; int height; // Reduce bar thickness and spacing until we can fit in the maximum height. while (true) { height = headerSize + map.size()*(barThickness + barSpacing); if (height <= maxHeight) break; barSpacing--; if (barSpacing == 1) break; height = headerSize + map.size()*(barThickness + barSpacing); if (height <= maxHeight) break; barThickness--; if (barThickness == 1) break; } boolean truncated = false; if (height > maxHeight) { truncated = true; height = maxHeight; } double scale = max * 1.1; System.out.println("<img src='https://chart.googleapis.com/chart?chtt=" + urlEncode(m.displayName) + "&chf=c||lg||0||FFFFFF||1||76A4FB||0|bg||s||EFEFEF&chs="+width+"x"+height+"&chd=t:" + valSb.toString().substring(0, valSb.length() - 1) + "&chds=0,"+ scale + "&chxt=y" + "&chxl=0:|" + names.substring(0, names.length() - 1) + "&chm=N *f*,000000,0,-1,10&lklk&chdlp=t&chco=660000|660033|660066|660099|6600CC|6600FF|663300|663333|663366|663399|6633CC|6633FF|666600|666633|666666&cht=bhg&chbh=" + barThickness + ",0," + barSpacing + "&nonsense=aaa.png'/>"); if (truncated) { System.err.println("WARNING: Not enough room to fit all bars in chart."); } } private static void addValue( EnumMap<measurements, Map<String, Double>> values, String name, double timeCreate, double timeSerializeDifferentObjects, double timeSerializeSameObject, double timeDeserializeNoFieldAccess, double timeDeserializeAndCheckShallow, double timeDeserializeAndCheck, double totalTime, double length, double lengthDeflate) { values.get(measurements.timeSerializeDifferentObjects).put(name, timeSerializeDifferentObjects); values.get(measurements.timeSerializeSameObject).put(name, timeSerializeSameObject); values.get(measurements.timeDeserializeNoFieldAccess).put(name, timeDeserializeNoFieldAccess); values.get(measurements.timeDeserializeAndCheckShallow).put(name, timeDeserializeAndCheckShallow); values.get(measurements.timeDeserializeAndCheck).put(name, timeDeserializeAndCheck); values.get(measurements.totalTime).put(name, totalTime); values.get(measurements.length).put(name, length); values.get(measurements.lengthDeflate).put(name, lengthDeflate); values.get(measurements.timeCreate).put(name, timeCreate); } private static <J> void warmCreation(TestCaseRunner<J> runner, long warmupTime) throws Exception { // Instead of fixed counts, let's try to prime by running for N seconds long endTime = System.currentTimeMillis() + warmupTime; do { runner.run(Create, 10); } while (System.currentTimeMillis() < endTime); } private static <J> void warmSerialization(TestCaseRunner<J> runner, long warmupTime) throws Exception { // Instead of fixed counts, let's try to prime by running for N seconds long endTime = System.currentTimeMillis() + warmupTime; do { runner.run(Serialize, 10); } while (System.currentTimeMillis() < endTime); } private static <J> void warmDeserialization(TestCaseRunner<J> runner, long warmupTime) throws Exception { // Instead of fixed counts, let's try to prime by running for N seconds long endTime = System.currentTimeMillis() + warmupTime; do { runner.run(DeserializeAndCheck, 10); } while (System.currentTimeMillis() < endTime); } }