001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.tools.globalfeature; 031 032import java.io.File; 033import java.io.IOException; 034import java.lang.reflect.Method; 035import java.net.MalformedURLException; 036import java.net.URL; 037import java.net.URLClassLoader; 038import java.util.ArrayList; 039import java.util.List; 040import java.util.concurrent.Callable; 041import java.util.concurrent.ExecutorService; 042import java.util.concurrent.Executors; 043 044import org.kohsuke.args4j.CmdLineException; 045import org.kohsuke.args4j.CmdLineParser; 046import org.kohsuke.args4j.Option; 047 048/** 049 * A tool for running other tools in parallel, in a similar manner to a UNIX 050 * Makefile. 051 * 052 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 053 * 054 */ 055public class ParallelExecutor { 056 @Option(name = "--input", aliases = "-i", usage = "input directory", required = true) 057 private File inputBase; 058 059 @Option(name = "--output", aliases = "-o", usage = "output directory", required = true) 060 private File outputBase; 061 062 @Option(name = "--output-ext", aliases = "-e", usage = "output extension", required = true) 063 private File outputExt; 064 065 @Option(name = "--input-regex", aliases = "-r", usage = "input regex") 066 private String inputRegex; 067 068 @Option(name = "--class", aliases = "-c", usage = "class to run", required = true) 069 private String commandClass; 070 071 @Option(name = "--args", aliases = "-a", usage = "arguments to pass", required = true) 072 private String commandArgs; 073 074 @Option(name = "--force", aliases = "-f", usage = "force regenerate") 075 private boolean force = false; 076 077 @Option(name = "-j", usage = "n paralled jobs", required = false) 078 private int njobs = 1; 079 080 @Option(name = "--timing", aliases = "-t", usage = "print timing information") 081 private boolean timing = false; 082 083 @Option(name = "--verbose", aliases = "-v", usage = "print timing information") 084 private boolean verbose = false; 085 086 // private TLongObjectHashMap<URLClassLoader> classLoaders = new 087 // TLongObjectHashMap<URLClassLoader>(); 088 089 synchronized Class<?> loadClass(String clzName) throws ClassNotFoundException { 090 // long id = Thread.currentThread().getId(); 091 URLClassLoader tmp; 092 093 // if (classLoaders.containsKey(id)) { 094 // tmp = classLoaders.get(id); 095 // } else { 096 tmp = new URLClassLoader(new URL[] { getClassPath() }) { 097 @Override 098 public synchronized Class<?> loadClass(String name) throws ClassNotFoundException { 099 Class<?> cl = null; 100 try { 101 cl = findLoadedClass(name); 102 if (cl != null) 103 return cl; 104 cl = findClass(name); 105 } catch (final ClassNotFoundException e) { 106 cl = super.loadClass(name); 107 } 108 109 return cl; 110 } 111 }; 112 113 // classLoaders.put(id, tmp); 114 // } 115 116 return tmp.loadClass(clzName); 117 } 118 119 private static URL getClassPath() { 120 final String resName = ParallelExecutor.class.getName().replace('.', '/') + ".class"; 121 final String loc = ParallelExecutor.class.getClassLoader().getResource(resName).toExternalForm(); 122 URL cp; 123 try { 124 cp = new URL(loc.substring(0, loc.length() - resName.length())); 125 } catch (final MalformedURLException e) { 126 throw new RuntimeException(e); 127 } 128 return cp; 129 } 130 131 class Job implements Callable<Boolean> { 132 File file; 133 134 public Job(File file) { 135 this.file = file; 136 } 137 138 @Override 139 public Boolean call() throws Exception { 140 try { 141 Class<?> clz = null; 142 try { 143 clz = loadClass(commandClass); 144 // clz = Class.forName(commandClass); 145 } catch (final ClassNotFoundException e) { 146 clz = loadClass("org.openimaj.tools.globalfeature." + commandClass); 147 // clz = 148 // Class.forName("uk.ac.soton.ecs.jsh2."+commandClass); 149 } 150 151 final Method m = clz.getMethod("main", String[].class); 152 153 final File output = getOutput(file); 154 output.getParentFile().mkdirs(); 155 156 String theCommandArgs = commandArgs.replace("__IN__", file.getAbsolutePath()); 157 theCommandArgs = theCommandArgs.replace("__OUT__", output.getAbsolutePath()); 158 159 if (verbose) 160 System.err.println("java " + commandClass + " " + theCommandArgs); 161 162 m.invoke(null, new Object[] { theCommandArgs.split(" ") }); 163 } catch (final Throwable e) { 164 e.printStackTrace(); 165 return false; 166 } 167 168 return true; 169 } 170 } 171 172 void execute() throws InterruptedException { 173 final double t1 = System.currentTimeMillis(); 174 final List<File> inputFiles = getInputs(); 175 final double t2 = System.currentTimeMillis(); 176 if (timing) { 177 System.out.println("Input files:\t" + inputFiles.size()); 178 System.out.println("Search time:\t" + ((t2 - t1) / 1000.0) + " secs"); 179 } 180 181 final List<Job> jobs = new ArrayList<Job>(); 182 183 for (final File f : inputFiles) 184 jobs.add(new Job(f)); 185 final double t3 = System.currentTimeMillis(); 186 if (timing) 187 System.out.println("Job create:\t" + ((t3 - t2) / 1000.0) + " secs"); 188 189 final ExecutorService es = Executors.newFixedThreadPool(njobs); 190 es.invokeAll(jobs); 191 es.shutdown(); 192 193 final double t4 = System.currentTimeMillis(); 194 if (timing) { 195 System.out.println("Proc time total:\t" + ((t4 - t3) / 1000.0) + " secs"); 196 System.out.println("Proc time per file:\t" + (((t4 - t3) / 1000.0) / inputFiles.size()) + " secs"); 197 198 System.out.println("Norm Proc time total:\t" + (njobs * (t4 - t3) / 1000) + " secs"); 199 System.out.println("Norm Proc time per file:\t" + (njobs * ((t4 - t3) / 1000.0) / inputFiles.size()) 200 + " secs"); 201 } 202 } 203 204 private List<File> getInputs() { 205 final List<File> files = new ArrayList<File>(); 206 207 getInputs(files, inputBase); 208 209 return files; 210 } 211 212 private void getInputs(List<File> files, File dir) { 213 for (final File f : dir.listFiles()) { 214 if (f.isDirectory()) { 215 getInputs(files, f); 216 } else { 217 // check matches regex 218 if (inputRegex == null || f.getName().matches(inputRegex)) { 219 // check output 220 final File output = getOutput(f); 221 222 if (!output.exists() || force) { 223 files.add(f); 224 } 225 } 226 } 227 } 228 } 229 230 private File getOutput(File f) { 231 final File tmp = new File(outputBase, f.getAbsolutePath().replace(inputBase.getAbsolutePath(), "")); 232 String outputName = tmp.getName(); 233 if (tmp.getName().contains(".")) { 234 outputName = tmp.getName().substring(0, tmp.getName().lastIndexOf(".")); 235 } 236 return new File(tmp.getParent(), outputName + outputExt); 237 } 238 239 /** 240 * The main method of the tool. 241 * 242 * @param args 243 * @throws IOException 244 * @throws InterruptedException 245 */ 246 public static void main(String[] args) throws IOException, InterruptedException { 247 final ParallelExecutor tool = new ParallelExecutor(); 248 249 final CmdLineParser parser = new CmdLineParser(tool); 250 251 try { 252 parser.parseArgument(args); 253 } catch (final CmdLineException e) { 254 System.err.println(e.getMessage()); 255 System.err 256 .println("Usage: java -cp GlobalFeaturesTool.jar uk.ac.soton.ecs.jsh2.ParallelExecutor [options...]"); 257 parser.printUsage(System.err); 258 return; 259 } 260 261 tool.execute(); 262 } 263}