DieHarder.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-3.5.0).
003  * Copyright (c) 2007-2016 Franz Wilhelmstötter
004  *
005  * Licensed under the Apache License, Version 2.0 (the "License");
006  * you may not use this file except in compliance with the License.
007  * You may obtain a copy of the License at
008  *
009  *      http://www.apache.org/licenses/LICENSE-2.0
010  *
011  * Unless required by applicable law or agreed to in writing, software
012  * distributed under the License is distributed on an "AS IS" BASIS,
013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  * See the License for the specific language governing permissions and
015  * limitations under the License.
016  *
017  * Author:
018  *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmx.at)
019  */
020 package org.jenetics.internal.util;
021 
022 import static java.lang.String.format;
023 import static java.util.Objects.requireNonNull;
024 import static java.util.regex.Pattern.quote;
025 import static java.util.stream.Collectors.counting;
026 import static java.util.stream.Collectors.groupingBy;
027 import static org.jenetics.internal.util.Equality.eq;
028 
029 import java.io.BufferedReader;
030 import java.io.IOException;
031 import java.io.InputStreamReader;
032 import java.io.OutputStream;
033 import java.text.NumberFormat;
034 import java.text.SimpleDateFormat;
035 import java.util.ArrayList;
036 import java.util.Date;
037 import java.util.List;
038 import java.util.Map;
039 import java.util.Optional;
040 import java.util.OptionalDouble;
041 import java.util.OptionalInt;
042 import java.util.Random;
043 
044 import org.jenetics.util.ISeq;
045 import org.jenetics.util.Seq;
046 
047 /**
048  * Class for testing a given random engine using the
049  * <a href="http://www.phy.duke.edu/~rgb/General/dieharder.php">dieharder</a>
050  * test application.
051  *
052  * @author <a href="mailto:franz.wilhelmstoetter@gmx.at">Franz Wilhelmstötter</a>
053  * @since 1.5
054  * @version 3.0
055  */
056 public final class DieHarder {
057 
058     /**
059      * Writes random numbers to an given data output stream.
060      *
061      * @author <a href="mailto:franz.wilhelmstoetter@gmx.at">Franz Wilhelmstötter</a>
062      * @since 1.5
063      * @version 3.0
064      */
065     private static final class Randomizer implements Runnable {
066         private final Random _random;
067         private final CountingOutputStream _out;
068 
069         Randomizer(final Random random, final OutputStream out) {
070             _random = requireNonNull(random);
071             _out = new CountingOutputStream(out);
072         }
073 
074         @Override
075         public void run() {
076             try {
077                 final byte[] data = new byte[4096];
078                 while (!Thread.currentThread().isInterrupted()) {
079                     _random.nextBytes(data);
080                     _out.write(data);
081                 }
082             } catch (IOException ignore) {
083             }
084         }
085 
086         long getCount() {
087             return _out.getCount();
088         }
089     }
090 
091     public static void main(final String[] args) throws Exception {
092         if ( args.length < 1) {
093             println("Usage: \n" +
094                 "   java org.jenetics.internal.util.DieHarder <random-class-name>");
095             return;
096         }
097 
098         test(args[0], ISeq.of(args).subSeq(1));
099     }
100 
101     private static void test(final String randomName, final Seq<String> args)
102         throws IOException, InterruptedException
103     {
104         final Random random;
105         try {
106             random = (Random)Class.forName(randomName).newInstance();
107             printt(
108                 "Testing: %s (%s)",
109                 randomName,
110                 new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date())
111             );
112         } catch (Exception e) {
113             println("Can't create random class '%s'.", randomName);
114             return;
115         }
116 
117         final List<String> dieharderArgs = new ArrayList<>();
118         dieharderArgs.add("dieharder");
119         dieharderArgs.addAll(args.asList());
120         dieharderArgs.add("-g");
121         dieharderArgs.add("200");
122 
123         printv();
124 
125         final long start = System.currentTimeMillis();
126         final ProcessBuilder builder = new ProcessBuilder(dieharderArgs);
127         final Process dieharder = builder.start();
128 
129         final Randomizer randomizer = new Randomizer(
130             random,
131             dieharder.getOutputStream()
132         );
133         final Thread randomizerThread = new Thread(randomizer);
134         randomizerThread.start();
135 
136         // The dieharder console output.
137         final BufferedReader stdout = new BufferedReader (
138             new InputStreamReader(dieharder.getInputStream())
139         );
140 
141         final List<Result> results = new ArrayList<>();
142         for (String l = stdout.readLine(); l != null; l = stdout.readLine()) {
143             Result.parse(l).ifPresent(results::add);
144             System.out.println(l);
145         }
146 
147         dieharder.waitFor();
148         randomizerThread.interrupt();
149 
150         final long millis = System.currentTimeMillis() - start;
151         final long sec = millis/1000;
152         final double megaBytes = randomizer.getCount()/(1024.0*1024.0);
153 
154         // Calculate statistics.
155         final Map<Assessment, Long> grouped = results.stream()
156             .collect(groupingBy(r -> r.assessment, counting()));
157 
158         final long passed = grouped.getOrDefault(Assessment.PASSED, 0L);
159         final long weak = grouped.getOrDefault(Assessment.WEAK, 0L);
160         final long failed = grouped.getOrDefault(Assessment.FAILED, 0L);
161 
162         final NumberFormat formatter = NumberFormat.getIntegerInstance();
163         formatter.setMinimumFractionDigits(3);
164         formatter.setMaximumFractionDigits(3);
165 
166         println("#=============================================================================#");
167         println(
168             "# %-76s#",
169             format("Summary: PASSED=%d, WEAK=%d, FAILED=%d", passed, weak, failed)
170         );
171         println(
172             "# %-76s#",
173             format("         %s MB of random data created with %s MB/sec",
174                 formatter.format(megaBytes),
175                 formatter.format(megaBytes/(millis/1000.0))
176             )
177         );
178         println("#=============================================================================#");
179         printt("Runtime: %d:%02d:%02d", sec/3600, (sec%3600)/60, sec%60);
180 
181     }
182 
183     private static void printt(final String title, final Object... args) {
184         println("#=============================================================================#");
185         println("# %-76s#", format(title, args));
186         println("#=============================================================================#");
187     }
188 
189     private static void printv() {
190         println("#=============================================================================#");
191         println(
192             "# %-76s#",
193             format("%s %s (%s) ", p("os.name"), p("os.version"), p("os.arch"))
194         );
195         println(
196             "# %-76s#",
197             format("java version \"%s\"", p("java.version"))
198         );
199         println(
200             "# %-76s#",
201             format("%s (build %s)", p("java.runtime.name"), p("java.runtime.version"))
202         );
203         println(
204             "# %-76s#",
205             format("%s (build %s)", p("java.vm.name"), p("java.vm.version"))
206         );
207         println("#=============================================================================#");
208     }
209 
210     private static String p(final String name) {
211         return System.getProperty(name);
212     }
213 
214     private static void println(final String pattern, final Object... args) {
215         System.out.println(format(pattern, args));
216     }
217 
218 
219 
220     /**
221      * Represents one DieHarder test result.
222      *
223      * @author <a href="mailto:franz.wilhelmstoetter@gmx.at">Franz Wilhelmstötter</a>
224      * @since 3.0
225      * @version 3.0
226      */
227     static final class Result {
228         final String testName;
229         final int ntup;
230         final int tsamples;
231         final int psamples;
232         final double pvalue;
233         final Assessment assessment;
234 
235         private Result(
236             final String testName,
237             final int ntup,
238             final int tsamples,
239             final int psamples,
240             final double pvalue,
241             final Assessment assessment
242         ) {
243             this.testName = testName;
244             this.ntup = ntup;
245             this.tsamples = tsamples;
246             this.psamples = psamples;
247             this.pvalue = pvalue;
248             this.assessment = assessment;
249         }
250 
251         static Optional<Result> parse(final String line) {
252             final String[] parts = line.split(quote("|"));
253 
254             if (parts.length == 6) {
255                 final String name = parts[0].trim();
256                 final OptionalInt ntup = toOptionalInt(parts[1].trim());
257                 final OptionalInt tsamples = toOptionalInt(parts[2].trim());
258                 final OptionalInt psamples = toOptionalInt(parts[3].trim());
259                 final OptionalDouble pvalue = toOptionalDouble(parts[4].trim());
260                 final Optional<Assessment> assessment = Assessment.of(parts[5].trim());
261 
262                 if (ntup.isPresent() &&
263                     tsamples.isPresent() &&
264                     psamples.isPresent() &&
265                     pvalue.isPresent() &&
266                     assessment.isPresent())
267                 {
268                     return Optional.of(new Result(
269                         name,
270                         ntup.getAsInt(),
271                         tsamples.getAsInt(),
272                         psamples.getAsInt(),
273                         pvalue.getAsDouble(),
274                         assessment.get()
275                     ));
276                 }
277             }
278 
279             return Optional.empty();
280         }
281 
282         private static OptionalInt toOptionalInt(final String value) {
283             try {
284                 return OptionalInt.of(Integer.parseInt(value));
285             } catch (NumberFormatException e) {
286                 return OptionalInt.empty();
287             }
288         }
289 
290         private static OptionalDouble toOptionalDouble(final String value) {
291             try {
292                 return OptionalDouble.of(Double.parseDouble(value));
293             } catch (NumberFormatException e) {
294                 return OptionalDouble.empty();
295             }
296         }
297 
298         @Override
299         public int hashCode() {
300             return Hash.of(getClass())
301                 .and(testName)
302                 .and(ntup)
303                 .and(tsamples)
304                 .and(psamples)
305                 .and(pvalue)
306                 .and(assessment).value();
307         }
308 
309         @Override
310         public boolean equals(final Object obj) {
311             return obj instanceof Result &&
312                 eq(testName, ((Result)obj).testName) &&
313                 eq(ntup, ((Result)obj).ntup) &&
314                 eq(tsamples, ((Result)obj).tsamples) &&
315                 eq(psamples, ((Result)obj).psamples) &&
316                 eq(pvalue, ((Result)obj).psamples) &&
317                 eq(assessment, ((Result)obj).assessment);
318         }
319 
320         @Override
321         public String toString() {
322             return format(
323                 "%s[ntup=%d, tsamples=%d, psamples=%d, pvalue=%f, assessment=%s]",
324                 testName, ntup, tsamples, psamples, pvalue, assessment
325             );
326         }
327     }
328 
329     static enum Assessment {
330         PASSED,
331         FAILED,
332         WEAK;
333 
334         static Optional<Assessment> of(final String assessment) {
335             switch (assessment) {
336                 case "PASSED": return Optional.of(PASSED);
337                 case "FAILED": return Optional.of(FAILED);
338                 case "WEAK": return Optional.of(WEAK);
339                 default: return Optional.empty();
340             }
341         }
342     }
343 
344     /**
345      * Counts the written bytes.
346      *
347      * @author <a href="mailto:franz.wilhelmstoetter@gmx.at">Franz Wilhelmstötter</a>
348      * @since 3.0
349      * @version 3.0
350      */
351     private static final class CountingOutputStream extends OutputStream {
352         private final OutputStream _delegate;
353         private long _count;
354 
355         CountingOutputStream(final OutputStream delegate) {
356             _delegate = requireNonNull(delegate);
357         }
358 
359         @Override
360         public void write(final byte[] b) throws IOException {
361             _delegate.write(b);
362             _count += b.length;
363         }
364 
365         @Override
366         public void write(final byte[] b, final int offset, final int length)
367             throws IOException
368         {
369             _delegate.write(b, offset, length);
370             _count += length;
371         }
372 
373         @Override
374         public void write(final int b) throws IOException {
375             _delegate.write(b);
376             _count += 1;
377         }
378 
379         long getCount() {
380             return _count;
381         }
382 
383     }
384 
385 }