Java tutorial
/** * Copyright (C) 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.caliper.report; import com.google.caliper.util.CommandLineParser; import com.google.caliper.LinearTranslation; import com.google.caliper.MeasurementType; import com.google.caliper.plugin.ReportRenderer; import com.google.caliper.SuiteRun; import com.google.caliper.Scenario; import com.google.caliper.ScenarioResult; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * * * @author somebody@google * @author Renat.Gilmanov */ public final class ReportGenerator { private static final int BAR_GRAPH_WIDTH = 30; private static final int UNITS_FOR_SCORE_100 = 1; private static final int UNITS_FOR_SCORE_10 = 1000000000; // 1 s private RendererRegistry registry; private String rendererName; /** * */ private static final LinearTranslation scoreTranslation = new LinearTranslation(Math.log(UNITS_FOR_SCORE_10), 10, Math.log(UNITS_FOR_SCORE_100), 100); /** * */ public static final Ordering<Entry<String, Integer>> UNIT_ORDERING = new Ordering<Entry<String, Integer>>() { @Override public int compare(Entry<String, Integer> a, Entry<String, Integer> b) { return a.getValue().compareTo(b.getValue()); } }; /** * */ private final List<Variable> variables; private final SuiteRun run; private final List<Scenario> scenarios; private final List<MeasurementType> orderedMeasurementTypes; private final MeasurementType type; private final double maxValue; private final double logMinValue; private final double logMaxValue; private final EnumMap<MeasurementType, Integer> decimalDigitsMap = new EnumMap<MeasurementType, Integer>( MeasurementType.class); private final EnumMap<MeasurementType, Double> divideByMap = new EnumMap<MeasurementType, Double>( MeasurementType.class); private final EnumMap<MeasurementType, String> unitMap = new EnumMap<MeasurementType, String>( MeasurementType.class); private final EnumMap<MeasurementType, Integer> measurementColumnLengthMap = new EnumMap<MeasurementType, Integer>( MeasurementType.class); private CommandLineParser arguments; // TODO: refactor /** * * @param run * @param registry * @param arguments */ public ReportGenerator(SuiteRun run, RendererRegistry registry, CommandLineParser arguments) { this.run = run; this.arguments = arguments; rendererName = arguments.getReportType(); this.registry = registry; // File pluginFolder = arguments.getPluginFolder(); // this.registry = new DefaultRendererRegistry( // (null != pluginFolder) ? pluginFolder : new File(DEFAULT_PLUGIN_LOCATION), // arguments); unitMap.put(MeasurementType.TIME, arguments.getTimeUnit()); unitMap.put(MeasurementType.INSTANCE, arguments.getInstanceUnit()); unitMap.put(MeasurementType.MEMORY, arguments.getMemoryUnit()); if (arguments.getMeasureMemory()) { orderedMeasurementTypes = Arrays.asList(MeasurementType.values()); } else { orderedMeasurementTypes = Arrays.asList(MeasurementType.TIME); } if (arguments.getPrimaryMeasurementType() != null) { this.type = arguments.getPrimaryMeasurementType(); } else { this.type = MeasurementType.TIME; } double min = Double.POSITIVE_INFINITY; double max = 0; Multimap<String, String> nameToValues = LinkedHashMultimap.create(); List<Variable> variablesBuilder = new ArrayList<Variable>(); for (Entry<Scenario, ScenarioResult> entry : this.run.getMeasurements().entrySet()) { Scenario scenario = entry.getKey(); double d = entry.getValue().getMeasurementSet(type).medianUnits(); min = Math.min(min, d); max = Math.max(max, d); for (Entry<String, String> variable : scenario.getVariables().entrySet()) { String name = variable.getKey(); nameToValues.put(name, variable.getValue()); } } for (Entry<String, Collection<String>> entry : nameToValues.asMap().entrySet()) { Variable variable = new Variable(entry.getKey(), entry.getValue()); variablesBuilder.add(variable); } /* * Figure out how much influence each variable has on the measured value. * We sum the measurements taken with each value of each variable. For * variable that have influence on the measurement, the sums will differ * by value. If the variable has little influence, the sums will be similar * to one another and close to the overall average. We take the standard * deviation across each variable's collection of sums. Higher standard * deviation implies higher influence on the measured result. */ double sumOfAllMeasurements = 0; for (ScenarioResult measurement : this.run.getMeasurements().values()) { sumOfAllMeasurements += measurement.getMeasurementSet(type).medianUnits(); } for (Variable variable : variablesBuilder) { int numValues = variable.values.size(); double[] sumForValue = new double[numValues]; for (Entry<Scenario, ScenarioResult> entry : this.run.getMeasurements().entrySet()) { Scenario scenario = entry.getKey(); sumForValue[variable.index(scenario)] += entry.getValue().getMeasurementSet(type).medianUnits(); } double mean = sumOfAllMeasurements / sumForValue.length; double stdDeviationSquared = 0; for (double value : sumForValue) { double distance = value - mean; stdDeviationSquared += distance * distance; } variable.stdDeviation = Math.sqrt(stdDeviationSquared / numValues); } this.variables = new StandardDeviationOrdering().reverse().sortedCopy(variablesBuilder); this.scenarios = new ByVariablesOrdering().sortedCopy(this.run.getMeasurements().keySet()); this.maxValue = max; this.logMinValue = Math.log(min); this.logMaxValue = Math.log(max); EnumMap<MeasurementType, Integer> digitsBeforeDecimalMap = new EnumMap<MeasurementType, Integer>( MeasurementType.class); EnumMap<MeasurementType, Integer> decimalPointMap = new EnumMap<MeasurementType, Integer>( MeasurementType.class); for (MeasurementType measurementType : orderedMeasurementTypes) { double maxForType = 0; double minForType = Double.POSITIVE_INFINITY; for (Entry<Scenario, ScenarioResult> entry : this.run.getMeasurements().entrySet()) { double d = entry.getValue().getMeasurementSet(measurementType).medianUnits(); minForType = Math.min(minForType, d); maxForType = Math.max(maxForType, d); } unitMap.put(measurementType, getUnit(unitMap.get(measurementType), measurementType, minForType)); divideByMap.put(measurementType, (double) getUnits(measurementType).get(unitMap.get(measurementType))); int numDigitsInMin = ceil(Math.log10(minForType)); decimalDigitsMap.put(measurementType, ceil(Math.max(0, ceil(Math.log10(divideByMap.get(measurementType))) + 3 - numDigitsInMin))); digitsBeforeDecimalMap.put(measurementType, Math.max(1, ceil(Math.log10(maxForType / divideByMap.get(measurementType))))); decimalPointMap.put(measurementType, decimalDigitsMap.get(measurementType) > 0 ? 1 : 0); measurementColumnLengthMap.put(measurementType, Math.max(maxForType > 0 ? digitsBeforeDecimalMap.get(measurementType) + decimalPointMap.get(measurementType) + decimalDigitsMap.get(measurementType) : 1, unitMap.get(measurementType).trim().length())); } } private String getUnit(String userSuppliedUnit, MeasurementType measurementType, double min) { Map<String, Integer> units = getUnits(measurementType); if (userSuppliedUnit == null) { List<Entry<String, Integer>> entries = UNIT_ORDERING.reverse().sortedCopy(units.entrySet()); for (Entry<String, Integer> entry : entries) { if (min / entry.getValue() >= 1) { return entry.getKey(); } } // if no unit works, just use the smallest available unit. return entries.get(entries.size() - 1).getKey(); } if (!units.keySet().contains(userSuppliedUnit)) { throw new RuntimeException("\"" + unitMap.get(measurementType) + "\" is not a valid unit."); } return userSuppliedUnit; } private Map<String, Integer> getUnits(MeasurementType measurementType) { Map<String, Integer> units = null; for (Entry<Scenario, ScenarioResult> entry : run.getMeasurements().entrySet()) { if (units == null) { units = entry.getValue().getMeasurementSet(measurementType).getUnitNames(); } else { if (!units.equals(entry.getValue().getMeasurementSet(measurementType).getUnitNames())) { throw new RuntimeException( "measurement sets for run contain multiple, incompatible unit" + " sets."); } } } if (units == null) { throw new RuntimeException("run has no measurements."); } if (units.isEmpty()) { throw new RuntimeException("no units specified."); } return units; } /** * A variable and the set of values to which it has been assigned. */ private static class Variable { private final String name; private final ImmutableList<String> values; private final int maxLength; private double stdDeviation; Variable(String name, Collection<String> values) { this.name = name; this.values = ImmutableList.copyOf(values); int maxLen = name.length(); for (String value : values) { maxLen = Math.max(maxLen, value.length()); } this.maxLength = maxLen; } String get(Scenario scenario) { return scenario.getVariables().get(name); } int index(Scenario scenario) { return values.indexOf(get(scenario)); } boolean isInteresting() { return values.size() > 1; } } /** * Orders the different variables by their standard deviation. This results * in an appropriate grouping of output values. */ private static class StandardDeviationOrdering extends Ordering<Variable> { public int compare(Variable a, Variable b) { return Double.compare(a.stdDeviation, b.stdDeviation); } } /** * Orders scenarios by the variables. */ private class ByVariablesOrdering extends Ordering<Scenario> { public int compare(Scenario a, Scenario b) { for (Variable variable : variables) { int aValue = variable.values.indexOf(variable.get(a)); int bValue = variable.values.indexOf(variable.get(b)); int diff = aValue - bValue; if (diff != 0) { return diff; } } return 0; } } /** * */ public void displayResults() { buildReport(); System.out.println(); printUninterestingVariables(); } /** * Prints a table of values. */ private void buildReport() { DefaultReportHeader header = new DefaultReportHeader(); // --- header (variable.name) StringBuilder variableBuilder = new StringBuilder(); for (Variable variable : variables) { if (variable.isInteresting()) { variableBuilder.append(String.format("%" + variable.maxLength + "s ", variable.name)); } } header.setScaledTitle(variableBuilder.toString()); EnumMap<MeasurementType, String> numbersFormatMap = new EnumMap<MeasurementType, String>( MeasurementType.class); // --- header (measurement.type) StringBuilder measurementBuilder = new StringBuilder(); for (MeasurementType measurementType : orderedMeasurementTypes) { if (measurementType != type) { measurementBuilder .append(String.format("%" + measurementColumnLengthMap.get(measurementType) + "s ", unitMap.get(measurementType).trim())); } numbersFormatMap.put(measurementType, "%" + measurementColumnLengthMap.get(measurementType) + "." + decimalDigitsMap.get(measurementType) + "f" + (type == measurementType ? "" : " ")); } header.setMeasurementHeader(measurementBuilder.toString()); // --- unit.type header.setValueTitle( String.format("%" + measurementColumnLengthMap.get(type) + "s", unitMap.get(type).trim())); DefaultBenchmarkReport report = new DefaultBenchmarkReport(); report.setHeader(header); // ---------------------------------------------- double sumOfLogs = 0.0; for (Scenario scenario : scenarios) { // --- scenario.name StringBuilder nameBuilder = new StringBuilder(); DefaultReportRow reportRow = new DefaultReportRow(); report.addRow(reportRow); int count = 0; for (Variable variable : variables) { if (variable.isInteresting()) { if (count > 0) { nameBuilder.append('_'); nameBuilder.append(variable.get(scenario)); } else { nameBuilder.append(normalizeString(variable.get(scenario))); } } count++; } reportRow.setVariableName(nameBuilder.toString()); ScenarioResult measurement = run.getMeasurements().get(scenario); sumOfLogs += Math.log(measurement.getMeasurementSet(type).medianUnits()); // --- measuremente type for (MeasurementType measurementType : orderedMeasurementTypes) { if (measurementType != type) { System.out.printf(numbersFormatMap.get(measurementType), measurement.getMeasurementSet(measurementType).medianUnits() / divideByMap.get(measurementType)); } } // -- grapth reportRow.setValue(measurement.getMeasurementSet(type).medianUnits() / divideByMap.get(type)); reportRow.setValueType(type.toString()); reportRow.setScaledValue(getScaled(measurement.getMeasurementSet(type).medianUnits())); } double meanLogUnits = sumOfLogs / scenarios.size(); // arithmetic mean of logs, aka log of geometric mean report.setScore(scoreTranslation.translate(meanLogUnits)); ReportRenderer reportRenderer = registry.getRenderer(rendererName); if (null != reportRenderer) { reportRenderer.renderReport(report); } else { System.out.println("report renderer is null"); } } private String normalizeString(String value) { final int maxLength = 24; return (value.length() > maxLength) ? value.substring(0, maxLength) : value; } /** * Prints variables with only one unique value. */ private void printUninterestingVariables() { for (Variable variable : variables) { if (!variable.isInteresting()) { System.out.println(variable.name + ": " + Iterables.getOnlyElement(variable.values)); } } } /** * Returns a string containing a bar of proportional width to the specified * value. */ private int getScaled(double value) { int graphLength; LinearTranslation t = new LinearTranslation(logMinValue, 1, logMaxValue, BAR_GRAPH_WIDTH); graphLength = floor(t.translate(Math.log(value))); graphLength = Math.max(1, graphLength); graphLength = Math.min(BAR_GRAPH_WIDTH, graphLength); return graphLength; } @SuppressWarnings("NumericCastThatLosesPrecision") private static int floor(double d) { return (int) d; } @SuppressWarnings("NumericCastThatLosesPrecision") private static int ceil(double d) { return (int) Math.ceil(d); } }