1/*
2 * Copyright (C) 2011 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.runner;
18
19import static java.util.logging.Level.SEVERE;
20
21import com.google.caliper.api.ResultProcessor;
22import com.google.caliper.config.CaliperConfig;
23import com.google.caliper.config.InvalidConfigurationException;
24import com.google.caliper.config.ResultProcessorConfig;
25import com.google.caliper.model.Run;
26import com.google.caliper.model.Trial;
27import com.google.caliper.options.CaliperDirectory;
28import com.google.common.base.Charsets;
29import com.google.common.base.Optional;
30import com.google.common.io.Files;
31import com.google.gson.Gson;
32import com.google.gson.stream.JsonWriter;
33
34import org.joda.time.format.ISODateTimeFormat;
35
36import java.io.File;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.OutputStreamWriter;
40import java.util.logging.Logger;
41
42import javax.inject.Inject;
43
44/**
45 * {@link ResultProcessor} that dumps the output data to a file in JSON format. By default, the
46 * output will be dumped to a file called
47 * {@code ~/.caliper/results/[benchmark classname].[timestamp].json}; if it exists and is a file,
48 * the file will be overwritten.  The location can be overridden as either a file or a directory
49 * using either the {@code file} or {@code dir} options respectively.
50 */
51final class OutputFileDumper implements ResultProcessor {
52  private static final Logger logger = Logger.getLogger(OutputFileDumper.class.getName());
53
54  private final Run run;
55  private final Gson gson;
56  private final File resultFile;
57  private final File workFile;
58
59  private Optional<JsonWriter> writer = Optional.absent();
60
61  @Inject OutputFileDumper(Run run,
62      BenchmarkClass benchmarkClass,
63      Gson gson,
64      CaliperConfig caliperConfig,
65      @CaliperDirectory File caliperDirectory) throws InvalidConfigurationException {
66    this.run = run;
67    ResultProcessorConfig config = caliperConfig.getResultProcessorConfig(OutputFileDumper.class);
68    if (config.options().containsKey("file")) {
69      this.resultFile = new File(config.options().get("file"));
70      logger.finer("found an output file in the configuration");
71    } else if (config.options().containsKey("dir")) {
72      File dir = new File(config.options().get("dir"));
73      if (dir.isFile()) {
74        throw new InvalidConfigurationException("specified a directory, but it's a file");
75      }
76      this.resultFile = new File(dir, createFileName(benchmarkClass.name()));
77      logger.finer("found an output directory in the configuration");
78    } else {
79      this.resultFile =
80          new File(new File(caliperDirectory, "results"), createFileName(benchmarkClass.name()));
81      logger.fine("found no configuration");
82    }
83    logger.fine(String.format("using %s for results", resultFile));
84    this.gson = gson;
85    this.workFile = new File(resultFile.getPath() + ".tmp");
86  }
87
88  private String createFileName(String benchmarkName) {
89    return String.format("%s.%s.json", benchmarkName, createTimestamp());
90  }
91
92  private String createTimestamp() {
93    return ISODateTimeFormat.dateTimeNoMillis().print(run.startTime());
94  }
95
96  @Override public void processTrial(Trial trial) {
97    if (!writer.isPresent()) {
98      try {
99        Files.createParentDirs(workFile);
100        JsonWriter writer =
101            new JsonWriter(new OutputStreamWriter(new FileOutputStream(workFile), Charsets.UTF_8));
102        writer.setIndent("  ");  // always pretty print
103        writer.beginArray();
104        this.writer = Optional.of(writer);
105      } catch (IOException e) {
106        logger.log(SEVERE, String.format(
107            "An error occured writing trial %s. Results in %s will be incomplete.", trial.id(),
108            resultFile), e);
109      }
110    }
111    if (writer.isPresent()) {
112      gson.toJson(trial, Trial.class, writer.get());
113    }
114  }
115
116  @Override public void close() throws IOException {
117    if (writer.isPresent()) {
118      writer.get().endArray().close();
119    }
120    if (workFile.exists()) {
121      Files.move(workFile, resultFile);
122    }
123  }
124}
125