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 }
|