Home | History | Annotate | Download | only in runner
      1 /*
      2  * Copyright (C) 2014 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 
     17 package com.google.caliper.runner;
     18 
     19 import static com.google.common.base.Preconditions.checkArgument;
     20 
     21 import com.google.caliper.model.Run;
     22 import com.google.caliper.options.CaliperOptions;
     23 import com.google.common.base.Charsets;
     24 import com.google.common.io.Files;
     25 import com.google.common.util.concurrent.AbstractIdleService;
     26 
     27 import java.io.BufferedWriter;
     28 import java.io.File;
     29 import java.io.FileNotFoundException;
     30 import java.io.FileOutputStream;
     31 import java.io.OutputStreamWriter;
     32 import java.io.PrintWriter;
     33 import java.util.LinkedHashSet;
     34 import java.util.Set;
     35 
     36 import javax.annotation.concurrent.GuardedBy;
     37 import javax.inject.Inject;
     38 import javax.inject.Singleton;
     39 
     40 /**
     41  * A {@link TrialOutputFactory} implemented as a service that manages a directory either under
     42  * {@code /tmp} or in a user configured directory.
     43  *
     44  * <p>If there is a user configured directory, then no files will be deleted on service shutdown.
     45  * Otherwise the only way to ensure that the log files survive service shutdown is to explicitly
     46  * call {@link #persistFile(File)} with each file that should not be deleted.
     47  */
     48 @Singleton
     49 final class TrialOutputFactoryService
     50     extends AbstractIdleService implements TrialOutputFactory {
     51   private static final String LOG_DIRECTORY_PROPERTY = "worker.output";
     52 
     53   private final CaliperOptions options;
     54   private final Run run;
     55 
     56   @GuardedBy("this")
     57   private final Set<String> toDelete = new LinkedHashSet<String>();
     58 
     59   @GuardedBy("this")
     60   private File directory;
     61 
     62   @GuardedBy("this")
     63   private boolean persistFiles;
     64 
     65   @Inject TrialOutputFactoryService(Run run, CaliperOptions options) {
     66     this.run = run;
     67     this.options = options;
     68   }
     69 
     70   /** Returns the file to write trial output to. */
     71   @Override public FileAndWriter getTrialOutputFile(int trialNumber) throws FileNotFoundException {
     72     File dir;
     73     synchronized (this) {
     74       if (directory == null) {
     75         throw new RuntimeException(
     76             String.format("The output manager %s has not been started yet", this));
     77       }
     78       dir = directory;
     79     }
     80     File trialFile = new File(dir, String.format("trial-%d.log", trialNumber));
     81     synchronized (this) {
     82       if (!persistFiles) {
     83           toDelete.add(trialFile.getPath());
     84       }
     85     }
     86     return new FileAndWriter(trialFile,
     87         new PrintWriter(
     88             new BufferedWriter(
     89                 new OutputStreamWriter(
     90                     new FileOutputStream(trialFile),
     91                 Charsets.UTF_8))));
     92   }
     93 
     94   /**
     95    * Ensures that the given file will not be deleted on exit of the JVM, possibly by copying to a
     96    * new file.
     97    */
     98   @Override public synchronized void persistFile(File f) {
     99     if (!persistFiles) {
    100       checkArgument(toDelete.remove(f.getPath()), "%s was not created by the output manager", f);
    101     }
    102   }
    103 
    104   @Override protected synchronized void startUp() throws Exception {
    105     File directory;
    106     String dirName = options.configProperties().get(LOG_DIRECTORY_PROPERTY);
    107     boolean persistFiles = true;
    108     if (dirName != null) {
    109       directory = new File(dirName);
    110       if (!directory.exists()) {
    111         if (!directory.mkdirs()) {
    112           throw new Exception(
    113               String.format("Unable to create directory %s indicated by property %s",
    114                   dirName, LOG_DIRECTORY_PROPERTY));
    115         }
    116       } else if (!directory.isDirectory()) {
    117         throw new Exception(
    118             String.format("Configured directory %s indicated by property %s is not a directory",
    119                 dirName, LOG_DIRECTORY_PROPERTY));
    120       }
    121       // The directory exists and is a directory
    122       directory = new File(directory, String.format("run-%s", run.id()));
    123       if (!directory.mkdir()) {
    124         throw new Exception("Unable to create a run directory " + directory);
    125       }
    126     } else {
    127       // If none is configured then we don't care, just make a temp dir
    128       // TODO(lukes): it would be nice to use jdk7 java.nio.file.Files.createTempDir() which allows
    129       // us to specify a name, but caliper is still on jdk6.
    130       directory = Files.createTempDir();
    131       persistFiles = false;
    132     }
    133     this.directory = directory;
    134     this.persistFiles = persistFiles;
    135   }
    136 
    137   @Override protected synchronized void shutDown() throws Exception {
    138     if (!persistFiles) {
    139       // This is best effort, files to be deleted are already in a tmp directory.
    140       for (String f : toDelete) {
    141         new File(f).delete();
    142       }
    143       // This will only succeed if the directory is empty which is what we want.
    144       directory.delete();
    145     }
    146   }
    147 }
    148