blob: 57715c96b13a0ff01664556da58bcc03462958e4 [file] [log] [blame]
Paul Duffin7fc0b452015-11-10 17:45:15 +00001/*
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.caliper.UserException.DisplayUsageException;
20import com.google.caliper.UserException.ExceptionFromUserCodeException;
21import com.google.caliper.util.InterleavedReader;
22import com.google.common.base.Joiner;
23import com.google.common.base.Splitter;
24import com.google.common.collect.ImmutableList;
25import com.google.common.collect.ImmutableMap;
26import com.google.common.collect.ObjectArrays;
27import com.google.common.io.Closeables;
28import com.google.gson.JsonObject;
29
30import java.io.BufferedReader;
31import java.io.File;
32import java.io.FileFilter;
33import java.io.FileInputStream;
34import java.io.FileNotFoundException;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.InputStreamReader;
39import java.io.PrintStream;
40import java.net.HttpURLConnection;
41import java.net.InetSocketAddress;
42import java.net.Proxy;
43import java.net.URL;
44import java.text.SimpleDateFormat;
45import java.util.Date;
46import java.util.List;
47import java.util.Locale;
48import java.util.Map;
49import java.util.Map.Entry;
50import java.util.TimeZone;
51import java.util.regex.Pattern;
52
53/**
54 * Creates, executes and reports benchmark runs.
55 */
56public final class Runner {
57
58 private static final FileFilter UPLOAD_FILE_FILTER = new FileFilter() {
59 @Override public boolean accept(File file) {
60 return file.getName().endsWith(".xml") || file.getName().endsWith(".json");
61 }
62 };
63
64 private static final String FILE_NAME_DATE_FORMAT = "yyyy-MM-dd'T'HH-mm-ssZ";
65
66 private static final Splitter ARGUMENT_SPLITTER
67 = Splitter.on(Pattern.compile("\\s+")).omitEmptyStrings();
68
69 /** Command line arguments to the process */
70 private Arguments arguments;
71 private ScenarioSelection scenarioSelection;
72
73 private String createFileName(Result result) {
74 String timestamp = createTimestamp();
75 return String.format("%s.%s.json", result.getRun().getBenchmarkName(), timestamp);
76 }
77
78 private String createTimestamp() {
79 SimpleDateFormat dateFormat = new SimpleDateFormat(FILE_NAME_DATE_FORMAT, Locale.US);
80 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
81 dateFormat.setLenient(true);
82 return dateFormat.format(new Date());
83 }
84
85 public void run(String... args) {
86 this.arguments = Arguments.parse(args);
87 File resultsUploadFile = arguments.getUploadResultsFile();
88 if (resultsUploadFile != null) {
89 uploadResultsFileOrDir(resultsUploadFile);
90 return;
91 }
92 this.scenarioSelection = new ScenarioSelection(arguments);
93 if (arguments.getDebug()) {
94 debug();
95 return;
96 }
97 Result result = runOutOfProcess();
98 new ConsoleReport(result.getRun(), arguments).displayResults();
99 boolean saveResultsLocally = arguments.getSaveResultsFile() != null;
100 try {
101 postResults(result);
102 } catch (Exception e) {
103 System.out.println();
104 System.out.println(e);
105 saveResultsLocally = true;
106 }
107
108 if (saveResultsLocally) {
109 saveResults(result);
110 }
111 }
112
113 void uploadResultsFileOrDir(File resultsFileOrDir) {
114 try {
115 if (resultsFileOrDir.isDirectory()) {
116 for (File resultsFile : resultsFileOrDir.listFiles(UPLOAD_FILE_FILTER)) {
117 uploadResults(resultsFile);
118 }
119 } else {
120 uploadResults(resultsFileOrDir);
121 }
122 } catch (Exception e) {
123 throw new RuntimeException("uploading XML file failed", e);
124 }
125 }
126
127 private void uploadResults(File resultsUploadFile) throws IOException {
128 System.out.println();
129 System.out.println("Uploading " + resultsUploadFile.getCanonicalPath());
130 InputStream inputStream = new FileInputStream(resultsUploadFile);
131 try {
132 Result result = new ResultsReader().getResult(inputStream);
133 postResults(result);
134 } finally {
135 inputStream.close();
136 }
137 }
138
139 private void saveResults(Result result) {
140 File resultsFile = arguments.getSaveResultsFile();
141 File destinationFile;
142 if (resultsFile == null) {
143 File dir = new File("./caliper-results");
144 dir.mkdirs();
145 destinationFile = new File(dir, createFileName(result));
146 } else if (resultsFile.exists() && resultsFile.isDirectory()) {
147 destinationFile = new File(resultsFile, createFileName(result));
148 } else {
149 // assume this is a file
150 File parent = resultsFile.getParentFile();
151 if (parent != null) {
152 parent.mkdirs();
153 }
154 destinationFile = resultsFile;
155 }
156
157 PrintStream filePrintStream;
158 try {
159 filePrintStream = new PrintStream(new FileOutputStream(destinationFile));
160 } catch (FileNotFoundException e) {
161 throw new RuntimeException("can't open " + destinationFile, e);
162 }
163 String resultJson = Json.getGsonInstance().toJson(result);
164 try {
165 System.out.println();
166 System.out.println("Writing results to " + destinationFile.getCanonicalPath());
167 filePrintStream.print(resultJson);
168 } catch (Exception e) {
169 System.out.println(e);
170 System.out.println("Failed to write results to file, writing to standard out instead:");
171 System.out.println(resultJson);
172 System.out.flush();
173 } finally {
174 filePrintStream.close();
175 }
176 }
177
178 private void postResults(Result result) {
179 CaliperRc caliperrc = CaliperRc.INSTANCE;
180 String postUrl = caliperrc.getPostUrl();
181 String apiKey = caliperrc.getApiKey();
182 if (postUrl == null || apiKey == null) {
183 // TODO: probably nicer to show a message if only one is null
184 return;
185 }
186
187 try {
188 URL url = new URL(postUrl + apiKey + "/" + result.getRun().getBenchmarkName());
189 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(getProxy());
190 urlConnection.setDoOutput(true);
191 String resultJson = Json.getGsonInstance().toJson(result);
192 urlConnection.getOutputStream().write(resultJson.getBytes());
193 if (urlConnection.getResponseCode() == 200) {
194 System.out.println("");
195 System.out.println("View current and previous benchmark results online:");
196 BufferedReader in = new BufferedReader(
197 new InputStreamReader(urlConnection.getInputStream()));
198 System.out.println(" " + in.readLine());
199 in.close();
200 return;
201 }
202
203 System.out.println("Posting to " + postUrl + " failed: "
204 + urlConnection.getResponseMessage());
205 BufferedReader reader = new BufferedReader(
206 new InputStreamReader(urlConnection.getInputStream()));
207 String line;
208 while ((line = reader.readLine()) != null) {
209 System.out.println(line);
210 }
211 reader.close();
212 } catch (IOException e) {
213 throw new RuntimeException("Posting to " + postUrl + " failed.", e);
214 }
215 }
216
217 private Proxy getProxy() {
218 String proxyAddress = CaliperRc.INSTANCE.getProxy();
219 if (proxyAddress == null) {
220 return Proxy.NO_PROXY;
221 }
222
223 String[] proxyHostAndPort = proxyAddress.trim().split(":");
224 return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(
225 proxyHostAndPort[0], Integer.parseInt(proxyHostAndPort[1])));
226 }
227
228 private ScenarioResult runScenario(Scenario scenario) {
229 MeasurementResult timeMeasurementResult = measure(scenario, MeasurementType.TIME);
230 MeasurementSet allocationMeasurements = null;
231 String allocationEventLog = null;
232 MeasurementSet memoryMeasurements = null;
233 String memoryEventLog = null;
234 if (arguments.getMeasureMemory()) {
235 MeasurementResult allocationsMeasurementResult =
236 measure(scenario, MeasurementType.INSTANCE);
237 allocationMeasurements = allocationsMeasurementResult.getMeasurements();
238 allocationEventLog = allocationsMeasurementResult.getEventLog();
239 MeasurementResult memoryMeasurementResult =
240 measure(scenario, MeasurementType.MEMORY);
241 memoryMeasurements = memoryMeasurementResult.getMeasurements();
242 memoryEventLog = memoryMeasurementResult.getEventLog();
243 }
244
245 return new ScenarioResult(timeMeasurementResult.getMeasurements(),
246 timeMeasurementResult.getEventLog(),
247 allocationMeasurements, allocationEventLog,
248 memoryMeasurements, memoryEventLog);
249 }
250
251 private static class MeasurementResult {
252 private final MeasurementSet measurements;
253 private final String eventLog;
254
255 MeasurementResult(MeasurementSet measurements, String eventLog) {
256 this.measurements = measurements;
257 this.eventLog = eventLog;
258 }
259
260 public MeasurementSet getMeasurements() {
261 return measurements;
262 }
263
264 public String getEventLog() {
265 return eventLog;
266 }
267 }
268
269 private MeasurementResult measure(Scenario scenario, MeasurementType type) {
270 Vm vm = new VmFactory().createVm(scenario);
271 // this must be done before starting the forked process on certain VMs
272 ProcessBuilder processBuilder = createCommand(scenario, vm, type)
273 .redirectErrorStream(true);
274 Process timeProcess;
275 try {
276 timeProcess = processBuilder.start();
277 } catch (IOException e) {
278 throw new RuntimeException("failed to start subprocess", e);
279 }
280
281 MeasurementSet measurementSet = null;
282 StringBuilder eventLog = new StringBuilder();
283 InterleavedReader reader = null;
284 try {
285 reader = new InterleavedReader(arguments.getMarker(),
286 new InputStreamReader(timeProcess.getInputStream()));
287 Object o;
288 while ((o = reader.read()) != null) {
289 if (o instanceof String) {
290 eventLog.append(o);
291 } else if (measurementSet == null) {
292 JsonObject jsonObject = (JsonObject) o;
293 measurementSet = Json.measurementSetFromJson(jsonObject);
294 } else {
295 throw new RuntimeException("Unexpected value: " + o);
296 }
297 }
298 } catch (IOException e) {
299 throw new RuntimeException(e);
300 } finally {
301 Closeables.closeQuietly(reader);
302 timeProcess.destroy();
303 }
304
305 if (measurementSet == null) {
306 String message = "Failed to execute " + Joiner.on(" ").join(processBuilder.command());
307 System.err.println(" " + message);
308 System.err.println(eventLog.toString());
309 throw new ConfigurationException(message);
310 }
311
312 return new MeasurementResult(measurementSet, eventLog.toString());
313 }
314
315 private ProcessBuilder createCommand(Scenario scenario, Vm vm, MeasurementType type) {
316 File workingDirectory = new File(System.getProperty("user.dir"));
317
318 String classPath = System.getProperty("java.class.path");
319 if (classPath == null || classPath.length() == 0) {
320 throw new IllegalStateException("java.class.path is undefined in " + System.getProperties());
321 }
322
323 ImmutableList.Builder<String> vmArgs = ImmutableList.builder();
324 vmArgs.addAll(ARGUMENT_SPLITTER.split(scenario.getVariables().get(Scenario.VM_KEY)));
325 if (type == MeasurementType.INSTANCE || type == MeasurementType.MEMORY) {
326 String allocationJarFile = System.getenv("ALLOCATION_JAR");
327 vmArgs.add("-javaagent:" + allocationJarFile);
328 }
329 vmArgs.addAll(vm.getVmSpecificOptions(type, arguments));
330
331 Map<String, String> vmParameters = scenario.getVariables(
332 scenarioSelection.getVmParameterNames());
333 for (String vmParameter : vmParameters.values()) {
334 vmArgs.addAll(ARGUMENT_SPLITTER.split(vmParameter));
335 }
336
337 ImmutableList.Builder<String> caliperArgs = ImmutableList.builder();
338 caliperArgs.add("--warmupMillis").add(Long.toString(arguments.getWarmupMillis()));
339 caliperArgs.add("--runMillis").add(Long.toString(arguments.getRunMillis()));
340 caliperArgs.add("--measurementType").add(type.toString());
341 caliperArgs.add("--marker").add(arguments.getMarker());
342
343 Map<String,String> userParameters = scenario.getVariables(
344 scenarioSelection.getUserParameterNames());
345 for (Entry<String, String> entry : userParameters.entrySet()) {
346 caliperArgs.add("-D" + entry.getKey() + "=" + entry.getValue());
347 }
348 caliperArgs.add(arguments.getSuiteClassName());
349
350 return vm.newProcessBuilder(workingDirectory, classPath,
351 vmArgs.build(), InProcessRunner.class.getName(), caliperArgs.build());
352 }
353
354 private void debug() {
355 try {
356 int debugReps = arguments.getDebugReps();
357 InProcessRunner runner = new InProcessRunner();
358 DebugMeasurer measurer = new DebugMeasurer(debugReps);
359 for (Scenario scenario : scenarioSelection.select()) {
360 System.out.println("running " + debugReps + " debug reps of " + scenario);
361 runner.run(scenarioSelection, scenario, measurer);
362 }
363 } catch (Exception e) {
364 throw new ExceptionFromUserCodeException(e);
365 }
366 }
367
368 private Result runOutOfProcess() {
369 Date executedDate = new Date();
370 ImmutableMap.Builder<Scenario, ScenarioResult> resultsBuilder = ImmutableMap.builder();
371
372 try {
373 List<Scenario> scenarios = scenarioSelection.select();
374
375 int i = 0;
376 for (Scenario scenario : scenarios) {
377 beforeMeasurement(i++, scenarios.size(), scenario);
378 ScenarioResult scenarioResult = runScenario(scenario);
379 afterMeasurement(arguments.getMeasureMemory(), scenarioResult);
380 resultsBuilder.put(scenario, scenarioResult);
381 }
382 System.out.println();
383
384 Environment environment = new EnvironmentGetter().getEnvironmentSnapshot();
385 return new Result(
386 new Run(resultsBuilder.build(), arguments.getSuiteClassName(), executedDate),
387 environment);
388 } catch (Exception e) {
389 throw new ExceptionFromUserCodeException(e);
390 }
391 }
392
393 private void beforeMeasurement(int index, int total, Scenario scenario) {
394 double percentDone = (double) index / total;
395 System.out.printf("%2.0f%% %s", percentDone * 100, scenario);
396 }
397
398 private void afterMeasurement(boolean memoryMeasured, ScenarioResult scenarioResult) {
399 String memoryMeasurements = "";
400 if (memoryMeasured) {
401 MeasurementSet instanceMeasurementSet =
402 scenarioResult.getMeasurementSet(MeasurementType.INSTANCE);
403 String instanceUnit =
404 ConsoleReport.UNIT_ORDERING.min(instanceMeasurementSet.getUnitNames().entrySet()).getKey();
405 MeasurementSet memoryMeasurementSet = scenarioResult.getMeasurementSet(MeasurementType.MEMORY);
406 String memoryUnit =
407 ConsoleReport.UNIT_ORDERING.min(memoryMeasurementSet.getUnitNames().entrySet()).getKey();
408 memoryMeasurements = String.format(", allocated %s%s for a total of %s%s",
409 Math.round(instanceMeasurementSet.medianUnits()), instanceUnit,
410 Math.round(memoryMeasurementSet.medianUnits()), memoryUnit);
411 }
412
413 MeasurementSet timeMeasurementSet = scenarioResult.getMeasurementSet(MeasurementType.TIME);
414 String unit =
415 ConsoleReport.UNIT_ORDERING.min(timeMeasurementSet.getUnitNames().entrySet()).getKey();
416 System.out.printf(" %.2f %s; \u03C3=%.2f %s @ %d trials%s%n", timeMeasurementSet.medianUnits(),
417 unit, timeMeasurementSet.standardDeviationUnits(), unit,
418 timeMeasurementSet.getMeasurements().size(), memoryMeasurements);
419 }
420
421 public static void main(String[] args) {
422 try {
423 new Runner().run(args);
424 System.exit(0); // user code may have leave non-daemon threads behind!
425 } catch (DisplayUsageException e) {
426 e.display();
427 System.exit(0);
428 } catch (UserException e) {
429 e.display();
430 System.exit(1);
431 }
432 }
433
434 @SuppressWarnings("unchecked") // temporary fakery
435 public static void main(Class<? extends Benchmark> suite, String[] args) {
436 main(ObjectArrays.concat(args, suite.getName()));
437 }
438}