blob: 790ffe0bf60b65051cefd0bf53519d85dcce3fa9 [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 }
Jesse Wilsone95457f2009-12-10 15:45:23 -0800132 measurementColumnLength = maxValue > 0
133 ? (int) Math.ceil(Math.log10(maxValue / divideBy)) + decimalDigits + 1
134 : 1;
Jesse Wilson109c1282009-12-08 13:45:25 -0800135 }
136
137 /**
138 * A parameter plus all of its values.
139 */
140 static class Parameter {
141 final String name;
142 final ImmutableList<String> values;
143 final int maxLength;
144 double stdDeviation;
145
146 public Parameter(String name, Collection<String> values) {
147 this.name = name;
148 this.values = ImmutableList.copyOf(values);
149
150 int maxLength = name.length();
151 for (String value : values) {
152 maxLength = Math.max(maxLength, value.length());
153 }
154 this.maxLength = maxLength;
155 }
156
157 String get(Run run) {
158 if (benchmarkKey.equals(name)) {
159 return run.getBenchmarkClass().getSimpleName();
160 } else if (vmKey.equals(name)) {
161 return run.getVm();
162 } else {
163 return run.getParameters().get(name);
164 }
165 }
166
167 int index(Run run) {
168 return values.indexOf(get(run));
169 }
170
171 boolean isInteresting() {
172 return values.size() > 1;
173 }
174 }
175
176 /**
177 * Orders the different parameters by their standard deviation. This results
178 * in an appropriate grouping of output values.
179 */
180 static class StandardDeviationOrdering extends Ordering<Parameter> {
181 public int compare(Parameter a, Parameter b) {
182 return Double.compare(a.stdDeviation, b.stdDeviation);
183 }
184 }
185
186 /**
187 * Orders runs by the parameters.
188 */
189 class ByParametersOrdering extends Ordering<Run> {
190 public int compare(Run a, Run b) {
191 for (Parameter parameter : parameters) {
192 int aValue = parameter.values.indexOf(parameter.get(a));
193 int bValue = parameter.values.indexOf(parameter.get(b));
194 int diff = aValue - bValue;
195 if (diff != 0) {
196 return diff;
197 }
198 }
199 return 0;
200 }
201 }
202
203 void displayResults() {
204 printValues();
205 System.out.println();
206 printUninterestingParameters();
207 }
208
209 /**
210 * Prints a table of values.
211 */
212 private void printValues() {
213 for (Parameter parameter : parameters) {
214 if (parameter.isInteresting()) {
215 System.out.printf("%" + parameter.maxLength + "s ", parameter.name);
216 }
217 }
218 System.out.printf("%" + measurementColumnLength + "s logarithmic runtime%n", units);
219
220 String numbersFormat = "%" + measurementColumnLength + "." + decimalDigits + "f %s%n";
221 for (Run run : runs) {
222 for (Parameter parameter : parameters) {
223 if (parameter.isInteresting()) {
224 System.out.printf("%" + parameter.maxLength + "s ", parameter.get(run));
225 }
226 }
227 double measurement = result.getMeasurements().get(run);
228 System.out.printf(numbersFormat, measurement / divideBy, bargraph(measurement));
229 }
230 }
231
232 /**
233 * Prints parameters with only one unique value.
234 */
235 private void printUninterestingParameters() {
236 for (Parameter parameter : parameters) {
237 if (!parameter.isInteresting()) {
238 System.out.println(parameter.name + ": " + Iterables.getOnlyElement(parameter.values));
239 }
240 }
241 }
242
243 /**
244 * Returns a string containing a bar of proportional width to the specified
245 * value.
246 */
247 private String bargraph(double value) {
248 double logValue = Math.log(value);
249 int numChars = (int) ((logValue / logMaxValue) * bargraphWidth);
250 StringBuilder result = new StringBuilder(numChars);
251 for (int i = 0; i < numChars; i++) {
252 result.append("|");
253 }
254 return result.toString();
255 }
256}