Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.rng.examples.stress; import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.io.IOException; import java.io.File; import java.io.PrintWriter; import java.io.FileWriter; import java.io.DataOutputStream; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.apache.commons.rng.UniformRandomProvider; /** * Class that can be used for testing a generator by piping the values * returned by its {@link UniformRandomProvider#nextInt()} method to a * program that reads {@code int} values from its standard input and * writes an analysis report to standard output. * * The <a href="http://www.phy.duke.edu/~rgb/General/dieharder.php"> * "dieharder"</a> test suite is such a software. * * Example of command line, assuming that "examples.jar" specifies this * class as the "main" class (see {@link #main(String[]) main} method): * <pre><code> * $ java -jar examples.jar \ * report/dh_ \ * 4 \ * org.apache.commons.rng.examples.stress.GeneratorsList \ * /usr/bin/dieharder -a -g 200 -Y 1 -k 2 * </code></pre> */ public class RandomStressTester { /** Comment prefix. */ private static final String C = "# "; /** New line. */ private static final String N = "\n"; /** Command line. */ private final List<String> cmdLine; /** Output prefix. */ private final String fileOutputPrefix; /** * Creates the application. * * @param cmd Command line. * @param outputPrefix Output prefix for file reports. */ private RandomStressTester(List<String> cmd, String outputPrefix) { final File exec = new File(cmd.get(0)); if (!exec.exists() || !exec.canExecute()) { throw new IllegalArgumentException("Program is not executable: " + exec); } cmdLine = new ArrayList<String>(cmd); fileOutputPrefix = outputPrefix; final File reportDir = new File(fileOutputPrefix).getParentFile(); if (!reportDir.exists() || !reportDir.isDirectory() || !reportDir.canWrite()) { throw new IllegalArgumentException("Invalid output directory: " + reportDir); } } /** * Program's entry point. * * @param args Application's arguments. * The order is as follows: * <ol> * <li>Output prefix: Filename prefix where the output of the analysis will * written to. The appended suffix is the index of the instance within the * list of generators to be tested.</li> * <li>Number of threads to use concurrently: One thread will process one of * the generators to be tested.</li> * <li>Name of a class that implements {@code Iterable<UniformRandomProvider>} * (and defines a default constructor): Each generator of the list will be * tested by one instance of the analyzer program</li> * <li>Path to the executable: this is the analyzer software that reads 32-bits * integers from stdin.</li> * <li>All remaining arguments are passed to the executable.</li> * </ol> */ public static void main(String[] args) { final String output = args[0]; final int numThreads = Integer.parseInt(args[1]); final Iterable<UniformRandomProvider> rngList = createGeneratorsList(args[2]); final List<String> cmdLine = new ArrayList<String>(); cmdLine.addAll(Arrays.asList(Arrays.copyOfRange(args, 3, args.length))); final RandomStressTester app = new RandomStressTester(cmdLine, output); try { app.run(rngList, numThreads); } catch (IOException e) { throw new RuntimeException(e); } } /** * Creates the tasks and starts the processes. * * @param generators List of generators to be analyzed. * @param numConcurrentTasks Number of concurrent tasks. * Twice as many threads will be started: one thread for the RNG and one * for the analyzer. * @throws IOException if an error occurs when writing to the disk. */ private void run(Iterable<UniformRandomProvider> generators, int numConcurrentTasks) throws IOException { // Parallel execution. final ExecutorService service = Executors.newFixedThreadPool(numConcurrentTasks); // Placeholder (output will be "null"). final List<Future<?>> execOutput = new ArrayList<Future<?>>(); // Run tasks. int count = 0; for (UniformRandomProvider rng : generators) { final File output = new File(fileOutputPrefix + (++count)); final Runnable r = new Task(rng, output); execOutput.add(service.submit(r)); } // Wait for completion (ignoring return value). try { for (Future<?> f : execOutput) { try { f.get(); } catch (ExecutionException e) { System.err.println(e.getCause().getMessage()); } } } catch (InterruptedException ignored) { } // Terminate all threads. service.shutdown(); } /** * Creates the list of generators to be tested. * * @param name Name of the class that contains the generators to be * analyzed. * @return the list of generators. * @throws IllegalStateException if an error occurs during instantiation. */ private static Iterable<UniformRandomProvider> createGeneratorsList(String name) { try { return (Iterable<UniformRandomProvider>) Class.forName(name).newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } /** * Pipes random numbers to the standard input of an analyzer. */ private class Task implements Runnable { /** Directory for reports of the tester processes. */ private final File output; /** RNG to be tested. */ private final UniformRandomProvider rng; /** * Creates the task. * * @param random RNG to be tested. * @param report Report file. */ Task(UniformRandomProvider random, File report) { rng = random; output = report; } /** {@inheritDoc} */ @Override public void run() { try { // Write header. printHeader(output, rng); // Start test suite. final ProcessBuilder builder = new ProcessBuilder(cmdLine); builder.redirectOutput(ProcessBuilder.Redirect.appendTo(output)); final Process testingProcess = builder.start(); final DataOutputStream sink = new DataOutputStream(testingProcess.getOutputStream()); final long startTime = System.nanoTime(); try { while (true) { sink.writeInt(rng.nextInt()); } } catch (IOException e) { // Hopefully getting here when the analyzing software terminates. } final long endTime = System.nanoTime(); // Write footer. printFooter(output, endTime - startTime); } catch (IOException e) { throw new RuntimeException("Failed to start task: " + e.getMessage()); } } } /** * @param output File. * @param rng Generator being tested. * @param cmdLine * @throws IOException if there was a problem opening or writing to * the {@code output} file. */ private void printHeader(File output, UniformRandomProvider rng) throws IOException { final StringBuilder sb = new StringBuilder(); sb.append(C).append(N); sb.append(C).append("RNG: ").append(rng.toString()).append(N); sb.append(C).append(N); sb.append(C).append("Java: ").append(System.getProperty("java.version")).append(N); sb.append(C).append("Runtime: ").append(System.getProperty("java.runtime.version", "?")).append(N); sb.append(C).append("JVM: ").append(System.getProperty("java.vm.name")).append(" ") .append(System.getProperty("java.vm.version")).append(N); sb.append(C).append("OS: ").append(System.getProperty("os.name")).append(" ") .append(System.getProperty("os.version")).append(" ").append(System.getProperty("os.arch")) .append(N); sb.append(C).append(N); sb.append(C).append("Analyzer: "); for (String s : cmdLine) { sb.append(s).append(" "); } sb.append(N); sb.append(C).append(N); final PrintWriter w = new PrintWriter(new FileWriter(output, true)); w.print(sb.toString()); w.close(); } /** * @param output File. * @param nanoTime Duration of the run. * @throws IOException if there was a problem opening or writing to * the {@code output} file. */ private void printFooter(File output, long nanoTime) throws IOException { final PrintWriter w = new PrintWriter(new FileWriter(output, true)); w.println(C); final double duration = ((double) nanoTime) * 1e-9 / 60; w.println(C + "Test duration: " + duration + " minutes"); w.println(C); w.close(); } }