blob: 537d56a144edc4528a72b0e2fdb250cc5d9538e7 [file] [log] [blame]
Jesse Wilson109c1282009-12-08 13:45:25 -08001/**
2 * Copyright (C) 2009 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.caliper;
18
19import com.google.common.collect.*;
20
21import java.util.*;
22
23/**
24 * Prints a report containing the tested values and the corresponding
25 * measurements. Measurements are grouped by variable using indentation.
26 * Alongside numeric values, quick-glance ascii art bar charts are printed.
27 * Sample output:
28 * <pre>
29 * benchmark d ns logarithmic runtime
30 * ConcatenationBenchmark 3.141592653589793 4397 ||||||||||||||||||||||||
31 * ConcatenationBenchmark -0.0 223 |||||||||||||||
32 * FormatterBenchmark 3.141592653589793 33999 ||||||||||||||||||||||||||||||
33 * FormatterBenchmark -0.0 26399 |||||||||||||||||||||||||||||
34 * </pre>
35 */
36final class ConsoleReport {
37
38 private static final int bargraphWidth = 30;
39 private static final String benchmarkKey = "benchmark";
40 private static final String vmKey = "vm";
41
42 private final List<Parameter> parameters;
43 private final Result result;
44 private final List<Run> runs;
45
46 private final double logMaxValue;
47 private final int decimalDigits;
48 private final double divideBy;
49 private final String units;
50 private final int measurementColumnLength;
51
52 public ConsoleReport(Result result) {
53 this.result = result;
54
55 double minValue = Double.POSITIVE_INFINITY;
56 double maxValue = 0;
57
58 Multimap<String, String> nameToValues = LinkedHashMultimap.create();
59 List<Parameter> parametersBuilder = new ArrayList<Parameter>();
60 for (Map.Entry<Run, Double> entry : result.getMeasurements().entrySet()) {
61 Run run = entry.getKey();
62 double d = entry.getValue();
63
64 minValue = minValue < d ? minValue : d;
65 maxValue = maxValue > d ? maxValue : d;
66
67 for (Map.Entry<String, String> parameter : run.getParameters().entrySet()) {
68 String name = parameter.getKey();
69 nameToValues.put(name, parameter.getValue());
70 }
71
72 nameToValues.put(benchmarkKey, run.getBenchmarkClass().getSimpleName());
73 nameToValues.put(vmKey, run.getVm());
74 }
75
76 for (Map.Entry<String, Collection<String>> entry : nameToValues.asMap().entrySet()) {
77 Parameter parameter = new Parameter(entry.getKey(), entry.getValue());
78 parametersBuilder.add(parameter);
79 }
80
81 /*
82 * Figure out how much influence each parameter has on the measured value.
83 * We sum the measurements taken with each value of each parameter. For
84 * parameters that have influence on the measurement, the sums will differ
85 * by value. If the parameter has little influence, the sums will be similar
86 * to one another and close to the overall average. We take the standard
87 * deviation across each parameters collection of sums. Higher standard
88 * deviation implies higher influence on the measured result.
89 */
90 double sumOfAllMeasurements = 0;
91 for (double measurement : result.getMeasurements().values()) {
92 sumOfAllMeasurements += measurement;
93 }
94 for (Parameter parameter : parametersBuilder) {
95 int numValues = parameter.values.size();
96 double[] sumForValue = new double[numValues];
97 for (Map.Entry<Run, Double> entry : result.getMeasurements().entrySet()) {
98 Run run = entry.getKey();
99 sumForValue[parameter.index(run)] += entry.getValue();
100 }
101 double mean = sumOfAllMeasurements / sumForValue.length;
102 double stdDeviationSquared = 0;
103 for (double value : sumForValue) {
104 double distance = value - mean;
105 stdDeviationSquared += distance * distance;
106 }
107 parameter.stdDeviation = Math.sqrt(stdDeviationSquared / numValues);
108 }
109
110 this.parameters = new StandardDeviationOrdering().reverse().sortedCopy(parametersBuilder);
111 this.runs = new ByParametersOrdering().sortedCopy(result.getMeasurements().keySet());
112 this.logMaxValue = Math.log(maxValue);
113
114 int numDigitsInMin = (int) Math.ceil(Math.log10(minValue));
115 if (numDigitsInMin > 9) {
116 divideBy = 1000000000;
117 decimalDigits = Math.max(0, 9 + 3 - numDigitsInMin);
118 units = "s";
119 } else if (numDigitsInMin > 6) {
120 divideBy = 1000000;
121 decimalDigits = Math.max(0, 6 + 3 - numDigitsInMin);
122 units = "ms";
123 } else if (numDigitsInMin > 3) {
124 divideBy = 1000;
125 decimalDigits = Math.max(0, 3 + 3 - numDigitsInMin);
126 units = "us";
127 } else {
128 divideBy = 1;
129 decimalDigits = 0;
130 units = "ns";
131 }
132 measurementColumnLength = (int) Math.ceil(Math.log10(maxValue / divideBy)) + decimalDigits + 1;
133 }
134
135 /**
136 * A parameter plus all of its values.
137 */
138 static class Parameter {
139 final String name;
140 final ImmutableList<String> values;
141 final int maxLength;
142 double stdDeviation;
143
144 public Parameter(String name, Collection<String> values) {
145 this.name = name;
146 this.values = ImmutableList.copyOf(values);
147
148 int maxLength = name.length();
149 for (String value : values) {
150 maxLength = Math.max(maxLength, value.length());
151 }
152 this.maxLength = maxLength;
153 }
154
155 String get(Run run) {
156 if (benchmarkKey.equals(name)) {
157 return run.getBenchmarkClass().getSimpleName();
158 } else if (vmKey.equals(name)) {
159 return run.getVm();
160 } else {
161 return run.getParameters().get(name);
162 }
163 }
164
165 int index(Run run) {
166 return values.indexOf(get(run));
167 }
168
169 boolean isInteresting() {
170 return values.size() > 1;
171 }
172 }
173
174 /**
175 * Orders the different parameters by their standard deviation. This results
176 * in an appropriate grouping of output values.
177 */
178 static class StandardDeviationOrdering extends Ordering<Parameter> {
179 public int compare(Parameter a, Parameter b) {
180 return Double.compare(a.stdDeviation, b.stdDeviation);
181 }
182 }
183
184 /**
185 * Orders runs by the parameters.
186 */
187 class ByParametersOrdering extends Ordering<Run> {
188 public int compare(Run a, Run b) {
189 for (Parameter parameter : parameters) {
190 int aValue = parameter.values.indexOf(parameter.get(a));
191 int bValue = parameter.values.indexOf(parameter.get(b));
192 int diff = aValue - bValue;
193 if (diff != 0) {
194 return diff;
195 }
196 }
197 return 0;
198 }
199 }
200
201 void displayResults() {
202 printValues();
203 System.out.println();
204 printUninterestingParameters();
205 }
206
207 /**
208 * Prints a table of values.
209 */
210 private void printValues() {
211 for (Parameter parameter : parameters) {
212 if (parameter.isInteresting()) {
213 System.out.printf("%" + parameter.maxLength + "s ", parameter.name);
214 }
215 }
216 System.out.printf("%" + measurementColumnLength + "s logarithmic runtime%n", units);
217
218 String numbersFormat = "%" + measurementColumnLength + "." + decimalDigits + "f %s%n";
219 for (Run run : runs) {
220 for (Parameter parameter : parameters) {
221 if (parameter.isInteresting()) {
222 System.out.printf("%" + parameter.maxLength + "s ", parameter.get(run));
223 }
224 }
225 double measurement = result.getMeasurements().get(run);
226 System.out.printf(numbersFormat, measurement / divideBy, bargraph(measurement));
227 }
228 }
229
230 /**
231 * Prints parameters with only one unique value.
232 */
233 private void printUninterestingParameters() {
234 for (Parameter parameter : parameters) {
235 if (!parameter.isInteresting()) {
236 System.out.println(parameter.name + ": " + Iterables.getOnlyElement(parameter.values));
237 }
238 }
239 }
240
241 /**
242 * Returns a string containing a bar of proportional width to the specified
243 * value.
244 */
245 private String bargraph(double value) {
246 double logValue = Math.log(value);
247 int numChars = (int) ((logValue / logMaxValue) * bargraphWidth);
248 StringBuilder result = new StringBuilder(numChars);
249 for (int i = 0; i < numChars; i++) {
250 result.append("|");
251 }
252 return result.toString();
253 }
254}