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