1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.rng.examples.stress;
18
19 import org.apache.commons.rng.UniformRandomProvider;
20 import org.apache.commons.rng.core.source64.RandomLongSource;
21 import org.apache.commons.rng.simple.RandomSource;
22
23 import picocli.CommandLine.Command;
24 import picocli.CommandLine.Mixin;
25 import picocli.CommandLine.Option;
26 import picocli.CommandLine.Parameters;
27
28 import java.io.BufferedReader;
29 import java.io.BufferedWriter;
30 import java.io.File;
31 import java.io.IOException;
32 import java.nio.ByteOrder;
33 import java.nio.file.Files;
34 import java.nio.file.StandardOpenOption;
35 import java.text.SimpleDateFormat;
36 import java.time.Instant;
37 import java.time.LocalDateTime;
38 import java.time.ZoneId;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Date;
42 import java.util.Formatter;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.concurrent.Callable;
46 import java.util.concurrent.ExecutionException;
47 import java.util.concurrent.ExecutorService;
48 import java.util.concurrent.Executors;
49 import java.util.concurrent.Future;
50 import java.util.concurrent.TimeUnit;
51 import java.util.concurrent.locks.ReentrantLock;
52
53
54
55
56
57
58
59
60
61 @Command(name = "stress",
62 description = {"Run repeat trials of random data generators using a provided test application.",
63 "Data is transferred to the application sub-process via standard input."})
64 class StressTestCommand implements Callable<Void> {
65
66 private static final int ONE_THOUSAND = 1000;
67
68
69 @Mixin
70 private StandardOptions reusableOptions;
71
72
73 @Parameters(index = "0",
74 description = "The stress test executable.")
75 private File executable;
76
77
78 @Parameters(index = "1..*",
79 description = "The arguments to pass to the executable.",
80 paramLabel = "<argument>")
81 private List<String> executableArguments = new ArrayList<>();
82
83
84 @Option(names = {"--prefix"},
85 description = "Results file prefix (default: ${DEFAULT-VALUE}).")
86 private File fileOutputPrefix = new File("test_");
87
88
89 @Option(names = {"--stop-file"},
90 description = {"Stop file (default: <Results file prefix>.stop).",
91 "When created it will prevent new tasks from starting " +
92 "but running tasks will complete."})
93 private File stopFile;
94
95
96 @Option(names = {"-o", "--output-mode"},
97 description = {"Output mode for existing files (default: ${DEFAULT-VALUE}).",
98 "Valid values: ${COMPLETION-CANDIDATES}."})
99 private StressTestCommand.OutputMode outputMode = OutputMode.ERROR;
100
101
102 @Option(names = {"-l", "--list"},
103 description = {"List of random generators.",
104 "The default list is all known generators."},
105 paramLabel = "<genList>")
106 private File generatorsListFile;
107
108
109 @Option(names = {"-t", "--trials"},
110 description = {"The number of trials for each random generator.",
111 "Used only for the default list (default: ${DEFAULT-VALUE})."})
112 private int trials = 1;
113
114
115 @Option(names = {"--trial-offset"},
116 description = {"Offset to add to the trial number for output files (default: ${DEFAULT-VALUE}).",
117 "Use for parallel tests with the same output prefix."})
118 private int trialOffset;
119
120
121 @Option(names = {"-p", "--processors"},
122 description = {"Number of available processors (default: ${DEFAULT-VALUE}).",
123 "Number of concurrent tasks = ceil(processors / threadsPerTask)",
124 "threadsPerTask = applicationThreads + (ignoreJavaThread ? 0 : 1)"})
125 private int processors = Math.max(1, Runtime.getRuntime().availableProcessors());
126
127
128 @Option(names = {"--ignore-java-thread"},
129 description = {"Ignore the java RNG thread when computing concurrent tasks."})
130 private boolean ignoreJavaThread;
131
132
133 @Option(names = {"--threads"},
134 description = {"Number of threads to use for each application (default: ${DEFAULT-VALUE}).",
135 "Total threads per task includes an optional java thread."})
136 private int applicationThreads = 1;
137
138
139 @Option(names = {"--buffer-size"},
140 description = {"Byte-buffer size for the transferred data (default: ${DEFAULT-VALUE})."})
141 private int bufferSize = 8192;
142
143
144 @Option(names = {"-b", "--byte-order"},
145 description = {"Byte-order of the transferred data (default: ${DEFAULT-VALUE}).",
146 "Valid values: BIG_ENDIAN, LITTLE_ENDIAN."})
147 private ByteOrder byteOrder = ByteOrder.nativeOrder();
148
149
150 @Option(names = {"-r", "--reverse-bits"},
151 description = {"Reverse the bits in the data (default: ${DEFAULT-VALUE}).",
152 "Note: Generators may fail tests for a reverse sequence " +
153 "when passing using the standard sequence."})
154 private boolean reverseBits;
155
156
157 @Option(names = {"--high-bits"},
158 description = {"Use the upper 32-bits from the 64-bit long output.",
159 "Takes precedent over --low-bits."})
160 private boolean longHighBits;
161
162
163 @Option(names = {"--low-bits"},
164 description = {"Use the lower 32-bits from the 64-bit long output."})
165 private boolean longLowBits;
166
167
168 @Option(names = {"--raw64"},
169 description = {"Use 64-bit output (default is 32-bit).",
170 "This requires a 64-bit testing application and native 64-bit generators.",
171 "In 32-bit mode the output uses the upper then lower bits of 64-bit " +
172 "generators sequentially, each appropriately byte reversed for the platform."})
173 private boolean raw64;
174
175
176 @Option(names = {"-x", "--hex-seed"},
177 description = {"The hex-encoded random seed.",
178 "Seed conversion for multi-byte primitives use little-endian format.",
179 "Use to repeat tests. Not recommended for batch testing."})
180 private String byteSeed;
181
182
183
184
185
186
187
188
189 @Option(names = {"--hashcode"},
190 description = {"Combine the bits with a hashcode (default: ${DEFAULT-VALUE}).",
191 "System.identityHashCode(new Object()) ^ rng.nextInt()."})
192 private boolean xorHashCode;
193
194
195
196
197 @Option(names = {"--local-random"},
198 description = {"Combine the bits with ThreadLocalRandom (default: ${DEFAULT-VALUE}).",
199 "ThreadLocalRandom.current().nextInt() ^ rng.nextInt()."})
200 private boolean xorThreadLocalRandom;
201
202
203
204
205 @Option(names = {"--xor-rng"},
206 description = {"Combine the bits with a second generator.",
207 "xorRng.nextInt() ^ rng.nextInt().",
208 "Valid values: Any known RandomSource enum value."})
209 private RandomSource xorRandomSource;
210
211
212 @Option(names = {"--dry-run"},
213 description = "Perform a dry run where the generators and output files are created " +
214 "but the stress test is not executed.")
215 private boolean dryRun;
216
217
218 private ReentrantLock stopFileLock = new ReentrantLock(false);
219
220 private boolean stopFileExists;
221
222
223
224
225 private long stopFileTimestamp;
226
227
228
229
230 enum OutputMode {
231
232 ERROR,
233
234 SKIP,
235
236 APPEND,
237
238 OVERWRITE
239 }
240
241
242
243
244
245 @Override
246 public Void call() {
247 LogUtils.setLogLevel(reusableOptions.logLevel);
248 ProcessUtils.checkExecutable(executable);
249 ProcessUtils.checkOutputDirectory(fileOutputPrefix);
250 checkStopFileDoesNotExist();
251 final Iterable<StressTestData> stressTestData = createStressTestData();
252 printStressTestData(stressTestData);
253 runStressTest(stressTestData);
254 return null;
255 }
256
257
258
259
260
261
262
263 private void checkStopFileDoesNotExist() {
264 if (stopFile == null) {
265 stopFile = new File(fileOutputPrefix + ".stop");
266 }
267 if (stopFile.exists()) {
268 throw new ApplicationException("Stop file exists: " + stopFile);
269 }
270 }
271
272
273
274
275
276
277
278
279 private boolean isStopFileExists() {
280 stopFileLock.lock();
281 try {
282 if (!stopFileExists) {
283
284
285
286 final long timestamp = System.currentTimeMillis();
287 if (timestamp > stopFileTimestamp) {
288 checkStopFile(timestamp);
289 }
290 }
291 return stopFileExists;
292 } finally {
293 stopFileLock.unlock();
294 }
295 }
296
297
298
299
300
301
302
303 private void checkStopFile(final long timestamp) {
304 stopFileTimestamp = timestamp + TimeUnit.SECONDS.toMillis(2);
305 stopFileExists = stopFile.exists();
306 if (stopFileExists) {
307 LogUtils.info("Stop file detected: %s", stopFile);
308 LogUtils.info("No further tasks will start");
309 }
310 }
311
312
313
314
315
316
317
318
319
320 private Iterable<StressTestData> createStressTestData() {
321 if (generatorsListFile == null) {
322 return new StressTestDataList("", trials);
323 }
324
325 try (BufferedReader reader = Files.newBufferedReader(generatorsListFile.toPath())) {
326 return ListCommand.readStressTestData(reader);
327 } catch (final IOException ex) {
328 throw new ApplicationException("Failed to read generators list: " + generatorsListFile, ex);
329 }
330 }
331
332
333
334
335
336
337
338 private static void printStressTestData(Iterable<StressTestData> stressTestData) {
339 if (!LogUtils.isLoggable(LogUtils.LogLevel.DEBUG)) {
340 return;
341 }
342 try {
343 final StringBuilder sb = new StringBuilder("Testing generators").append(System.lineSeparator());
344 ListCommand.writeStressTestData(sb, stressTestData);
345 LogUtils.debug(sb.toString());
346 } catch (final IOException ex) {
347 throw new ApplicationException("Failed to show list of generators", ex);
348 }
349 }
350
351
352
353
354
355
356 private void runStressTest(Iterable<StressTestData> stressTestData) {
357 final List<String> command = ProcessUtils.buildSubProcessCommand(executable, executableArguments);
358
359 LogUtils.info("Set-up stress test ...");
360
361
362 final String basePath = fileOutputPrefix.getAbsolutePath();
363 checkExistingOutputFiles(basePath, stressTestData);
364
365 final int parallelTasks = getParallelTasks();
366
367 final ProgressTracker progressTracker = new ProgressTracker(parallelTasks);
368 final List<Runnable> tasks = createTasks(command, basePath, stressTestData, progressTracker);
369
370
371 final ExecutorService service = Executors.newFixedThreadPool(parallelTasks);
372
373 LogUtils.info("Running stress test ...");
374 LogUtils.info("Shutdown by creating stop file: %s", stopFile);
375 progressTracker.setTotal(tasks.size());
376 final List<Future<?>> taskList = submitTasks(service, tasks);
377
378
379 try {
380 for (final Future<?> f : taskList) {
381 try {
382 f.get();
383 } catch (final ExecutionException ex) {
384
385
386 LogUtils.error(ex.getCause(), ex.getMessage());
387 }
388 }
389 } catch (final InterruptedException ex) {
390
391 Thread.currentThread().interrupt();
392 throw new ApplicationException("Unexpected interruption: " + ex.getMessage(), ex);
393 } finally {
394
395 service.shutdown();
396 }
397
398 LogUtils.info("Finished stress test");
399 }
400
401
402
403
404
405
406
407
408 private void checkExistingOutputFiles(String basePath,
409 Iterable<StressTestData> stressTestData) {
410 if (outputMode == StressTestCommand.OutputMode.ERROR) {
411 for (final StressTestData testData : stressTestData) {
412 for (int trial = 1; trial <= testData.getTrials(); trial++) {
413
414 final File output = createOutputFile(basePath, testData, trial);
415 if (output.exists()) {
416 throw new ApplicationException(createExistingFileMessage(output));
417 }
418 }
419 }
420 }
421 }
422
423
424
425
426
427
428
429
430
431
432
433 private File createOutputFile(String basePath,
434 StressTestData testData,
435 int trial) {
436 return new File(String.format("%s%s_%d", basePath, testData.getId(), trial + trialOffset));
437 }
438
439
440
441
442
443
444
445 private static String createExistingFileMessage(File output) {
446 return "Existing output file: " + output;
447 }
448
449
450
451
452
453
454
455
456
457
458
459
460 private int getParallelTasks() {
461
462 final int availableProcessors = Math.max(1, processors);
463 final int threadsPerTask = Math.max(1, applicationThreads + (ignoreJavaThread ? 0 : 1));
464 final int parallelTasks = (int) Math.ceil((double) availableProcessors / threadsPerTask);
465 LogUtils.debug("Parallel tasks = %d (%d / %d)",
466 parallelTasks, availableProcessors, threadsPerTask);
467 return parallelTasks;
468 }
469
470
471
472
473
474
475
476
477
478
479
480 private List<Runnable> createTasks(List<String> command,
481 String basePath,
482 Iterable<StressTestData> stressTestData,
483 ProgressTracker progressTracker) {
484 final List<Runnable> tasks = new ArrayList<>();
485 for (final StressTestData testData : stressTestData) {
486 for (int trial = 1; trial <= testData.getTrials(); trial++) {
487
488 final File output = createOutputFile(basePath, testData, trial);
489 if (output.exists()) {
490
491 if (outputMode == StressTestCommand.OutputMode.ERROR) {
492 throw new ApplicationException(createExistingFileMessage(output));
493 }
494
495 LogUtils.info("%s existing output file: %s", outputMode, output);
496 if (outputMode == StressTestCommand.OutputMode.SKIP) {
497 continue;
498 }
499 }
500
501 final byte[] seed = createSeed(testData.getRandomSource());
502 UniformRandomProvider rng = testData.createRNG(seed);
503
504
505
506 if (longHighBits) {
507 rng = RNGUtils.createLongUpperBitsIntProvider(rng);
508 } else if (longLowBits) {
509 rng = RNGUtils.createLongLowerBitsIntProvider(rng);
510 }
511
512
513
514 if (xorHashCode) {
515 rng = RNGUtils.createHashCodeProvider(rng);
516 }
517 if (xorThreadLocalRandom) {
518 rng = RNGUtils.createThreadLocalRandomProvider(rng);
519 }
520 if (xorRandomSource != null) {
521 rng = RNGUtils.createXorProvider(
522 RandomSource.create(xorRandomSource),
523 rng);
524 }
525 if (reverseBits) {
526 rng = RNGUtils.createReverseBitsProvider(rng);
527 }
528
529
530
531
532
533
534 final Runnable r = new StressTestTask(testData.getRandomSource(), rng, seed,
535 output, command, this, progressTracker);
536 tasks.add(r);
537 }
538 }
539 return tasks;
540 }
541
542
543
544
545
546
547
548
549 private byte[] createSeed(RandomSource randomSource) {
550 if (byteSeed != null) {
551 try {
552 return Hex.decodeHex(byteSeed);
553 } catch (IllegalArgumentException ex) {
554 throw new ApplicationException("Invalid hex seed: " + ex.getMessage(), ex);
555 }
556 }
557 return randomSource.createSeed();
558 }
559
560
561
562
563
564
565
566
567 private static List<Future<?>> submitTasks(ExecutorService service,
568 List<Runnable> tasks) {
569 final List<Future<?>> taskList = new ArrayList<>(tasks.size());
570 tasks.forEach(r -> taskList.add(service.submit(r)));
571 return taskList;
572 }
573
574
575
576
577
578
579
580 static class ProgressTracker {
581
582 private static final long PROGRESS_INTERVAL = 500;
583
584
585 private int total;
586
587 private final int parallelTasks;
588
589 private int taskId;
590
591 private long[] startTimes;
592
593 private long[] sortedDurations;
594
595 private int completed;
596
597 private long nextReportTimestamp;
598
599
600
601
602
603
604 ProgressTracker(int parallelTasks) {
605 this.parallelTasks = parallelTasks;
606 }
607
608
609
610
611
612
613 void setTotal(int total) {
614 this.total = total;
615 startTimes = new long[total];
616 sortedDurations = new long[total];
617 }
618
619
620
621
622
623
624
625 int submitTask() {
626 int id;
627 synchronized (this) {
628 final long current = System.currentTimeMillis();
629 id = taskId++;
630 startTimes[id] = current;
631 reportProgress(current);
632 }
633 return id;
634 }
635
636
637
638
639
640
641
642 long endTask(int id) {
643 long duration;
644 synchronized (this) {
645 final long current = System.currentTimeMillis();
646 duration = current - startTimes[id];
647 sortedDurations[completed++] = duration;
648 reportProgress(current);
649 }
650 return duration;
651 }
652
653
654
655
656
657
658
659 private void reportProgress(long current) {
660
661 final int pending = total - taskId;
662 final int running = taskId - completed;
663
664
665
666
667
668
669
670
671 if (completed >= total ||
672 (current >= nextReportTimestamp && (running == parallelTasks || pending == 0))) {
673
674 nextReportTimestamp = current + PROGRESS_INTERVAL;
675 final StringBuilder sb = createStringBuilderWithTimestamp(current, pending, running, completed);
676 try (Formatter formatter = new Formatter(sb)) {
677 formatter.format(" (%.2f%%)", 100.0 * completed / total);
678 appendRemaining(sb, current, pending, running);
679 LogUtils.info(sb.toString());
680 }
681 }
682 }
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697 private static StringBuilder createStringBuilderWithTimestamp(long current,
698 int pending, int running, int completed) {
699 final StringBuilder sb = new StringBuilder(80);
700
701 final LocalDateTime time = LocalDateTime.ofInstant(
702 Instant.ofEpochMilli(current), ZoneId.systemDefault());
703 sb.append('[');
704 append00(sb, time.getHour()).append(':');
705 append00(sb, time.getMinute()).append(':');
706 append00(sb, time.getSecond());
707 return sb.append("] Pending ").append(pending)
708 .append(". Running ").append(running)
709 .append(". Completed ").append(completed);
710 }
711
712
713
714
715
716
717
718
719
720
721
722 private StringBuilder appendRemaining(StringBuilder sb, long current, int pending, int running) {
723 final long millis = getRemainingTime(current, pending, running);
724 if (millis == 0) {
725
726 return sb;
727 }
728
729
730 sb.append(". Remaining = ");
731 hms(sb, millis);
732 return sb;
733 }
734
735
736
737
738
739
740
741
742
743 private long getRemainingTime(long current, int pending, int running) {
744 final long taskTime = getEstimatedTaskTime();
745 if (taskTime == 0) {
746
747 return 0;
748 }
749
750
751
752
753
754
755
756
757
758
759
760
761
762 final int id = Math.max(0, taskId - 1);
763
764
765
766 long millis = (running == 0) ? 0 : getTimeRemaining(taskTime, current, startTimes[id]);
767
768
769
770
771
772
773
774
775
776
777
778
779
780 int batches = pending / parallelTasks;
781 millis += batches * taskTime;
782
783
784
785
786 int remainder = pending % parallelTasks;
787 if (remainder != 0) {
788
789 final int nthOldest = Math.max(0, id - parallelTasks + remainder);
790 millis += getTimeRemaining(taskTime, current, startTimes[nthOldest]);
791 }
792
793 return millis;
794 }
795
796
797
798
799
800
801 private long getEstimatedTaskTime() {
802 Arrays.sort(sortedDurations, 0, completed);
803
804
805
806 if (completed < 4) {
807 return sortedDurations[completed / 2];
808 }
809
810
811
812
813
814
815
816
817
818 int upper = completed - 1;
819 final long halfMax = sortedDurations[upper] / 2;
820
821 int lower = 0;
822 while (lower + 1 < upper) {
823 int mid = (lower + upper) >>> 1;
824 if (sortedDurations[mid] < halfMax) {
825 lower = mid;
826 } else {
827 upper = mid;
828 }
829 }
830
831 return sortedDurations[(upper + completed - 1) / 2];
832 }
833
834
835
836
837
838
839
840
841
842 private static long getTimeRemaining(long taskTime, long current, long startTime) {
843 final long endTime = startTime + taskTime;
844
845 return Math.max(0, endTime - current);
846 }
847
848
849
850
851
852
853
854
855 static StringBuilder hms(StringBuilder sb, final long millis) {
856 final long hours = TimeUnit.MILLISECONDS.toHours(millis);
857 long minutes = TimeUnit.MILLISECONDS.toMinutes(millis);
858 long seconds = TimeUnit.MILLISECONDS.toSeconds(millis);
859
860 seconds -= TimeUnit.MINUTES.toSeconds(minutes);
861 minutes -= TimeUnit.HOURS.toMinutes(hours);
862
863 append00(sb, hours).append(':');
864 append00(sb, minutes).append(':');
865 return append00(sb, seconds);
866 }
867
868
869
870
871
872
873
874
875 static StringBuilder append00(StringBuilder sb, long ticks) {
876 if (ticks == 0) {
877 sb.append("00");
878 } else {
879 if (ticks < 10) {
880 sb.append('0');
881 }
882 sb.append(ticks);
883 }
884 return sb;
885 }
886 }
887
888
889
890
891 private static class StressTestTask implements Runnable {
892
893 private static final String C = "# ";
894
895 private static final String N = System.lineSeparator();
896
897 private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
898
899 private static final String[] SI_UNITS = {"B", "kB", "MB", "GB", "TB", "PB", "EB"};
900
901 private static final long SI_UNIT_BASE = 1000;
902
903
904 private final RandomSource randomSource;
905
906 private final UniformRandomProvider rng;
907
908 private final byte[] seed;
909
910 private final File output;
911
912 private final List<String> command;
913
914 private final StressTestCommand cmd;
915
916 private final ProgressTracker progressTracker;
917
918
919 private long bytesUsed;
920
921
922
923
924
925
926
927
928
929
930
931
932 StressTestTask(RandomSource randomSource,
933 UniformRandomProvider rng,
934 byte[] seed,
935 File output,
936 List<String> command,
937 StressTestCommand cmd,
938 ProgressTracker progressTracker) {
939 this.randomSource = randomSource;
940 this.rng = rng;
941 this.seed = seed;
942 this.output = output;
943 this.command = command;
944 this.cmd = cmd;
945 this.progressTracker = progressTracker;
946 }
947
948
949 @Override
950 public void run() {
951 if (cmd.isStopFileExists()) {
952
953 return;
954 }
955
956 try {
957 printHeader();
958
959 Object exitValue;
960 long millis;
961 final int taskId = progressTracker.submitTask();
962 if (cmd.dryRun) {
963
964 exitValue = "N/A";
965 progressTracker.endTask(taskId);
966 millis = 0;
967 } else {
968
969 exitValue = runSubProcess();
970 millis = progressTracker.endTask(taskId);
971 }
972
973 printFooter(millis, exitValue);
974
975 } catch (final IOException ex) {
976 throw new ApplicationException("Failed to run task: " + ex.getMessage(), ex);
977 }
978 }
979
980
981
982
983
984
985
986 private Integer runSubProcess() throws IOException {
987
988 final ProcessBuilder builder = new ProcessBuilder(command);
989 builder.redirectOutput(ProcessBuilder.Redirect.appendTo(output));
990 builder.redirectErrorStream(true);
991 final Process testingProcess = builder.start();
992
993
994 try (RngDataOutput sink = RNGUtils.createDataOutput(rng, cmd.raw64,
995 testingProcess.getOutputStream(), cmd.bufferSize, cmd.byteOrder)) {
996 for (;;) {
997 sink.write(rng);
998 bytesUsed++;
999 }
1000 } catch (final IOException ignored) {
1001
1002 }
1003
1004 bytesUsed *= cmd.bufferSize;
1005
1006
1007
1008
1009
1010
1011
1012 return ProcessUtils.getExitValue(testingProcess, TimeUnit.SECONDS.toMillis(60));
1013 }
1014
1015
1016
1017
1018
1019
1020
1021 private void printHeader() throws IOException {
1022 final StringBuilder sb = new StringBuilder(200);
1023 sb.append(C).append(N)
1024 .append(C).append("RandomSource: ").append(randomSource.name()).append(N)
1025 .append(C).append("RNG: ").append(rng.toString()).append(N)
1026 .append(C).append("Seed: ").append(Hex.encodeHex(seed)).append(N)
1027 .append(C).append(N)
1028
1029
1030
1031
1032
1033 .append(C).append("Java: ").append(System.getProperty("java.version")).append(N);
1034 appendNameAndVersion(sb, "Runtime", "java.runtime.name", "java.runtime.version");
1035 appendNameAndVersion(sb, "JVM", "java.vm.name", "java.vm.version", "java.vm.info");
1036
1037 sb.append(C).append("OS: ").append(System.getProperty("os.name"))
1038 .append(' ').append(System.getProperty("os.version"))
1039 .append(' ').append(System.getProperty("os.arch")).append(N)
1040 .append(C).append("Native byte-order: ").append(ByteOrder.nativeOrder()).append(N)
1041 .append(C).append("Output byte-order: ").append(cmd.byteOrder).append(N);
1042 if (rng instanceof RandomLongSource) {
1043 sb.append(C).append("64-bit output: ").append(cmd.raw64).append(N);
1044 }
1045 sb.append(C).append(N)
1046 .append(C).append("Analyzer: ");
1047 for (final String s : command) {
1048 sb.append(s).append(' ');
1049 }
1050 sb.append(N)
1051 .append(C).append(N);
1052
1053 appendDate(sb, "Start").append(C).append(N);
1054
1055 write(sb, output, cmd.outputMode == StressTestCommand.OutputMode.APPEND);
1056 }
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066 private void printFooter(long millis,
1067 Object exitValue) throws IOException {
1068 final StringBuilder sb = new StringBuilder(200);
1069 sb.append(C).append(N);
1070
1071 appendDate(sb, "End").append(C).append(N);
1072
1073 sb.append(C).append("Exit value: ").append(exitValue).append(N)
1074 .append(C).append("Bytes used: ").append(bytesUsed)
1075 .append(" >= 2^").append(log2(bytesUsed))
1076 .append(" (").append(bytesToString(bytesUsed)).append(')').append(N)
1077 .append(C).append(N);
1078
1079 final double duration = millis * 1e-3 / 60;
1080 sb.append(C).append("Test duration: ").append(duration).append(" minutes").append(N)
1081 .append(C).append(N);
1082
1083 write(sb, output, true);
1084 }
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094 private static void write(StringBuilder sb,
1095 File output,
1096 boolean append) throws IOException {
1097 try (BufferedWriter w = append ?
1098 Files.newBufferedWriter(output.toPath(), StandardOpenOption.APPEND) :
1099 Files.newBufferedWriter(output.toPath())) {
1100 w.write(sb.toString());
1101 }
1102 }
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117 private static StringBuilder appendNameAndVersion(StringBuilder sb,
1118 String prefix,
1119 String nameKey,
1120 String versionKey,
1121 String... infoKeys) {
1122 appendPrefix(sb, prefix)
1123 .append(System.getProperty(nameKey, "?"))
1124 .append(" (build ")
1125 .append(System.getProperty(versionKey, "?"));
1126 for (final String key : infoKeys) {
1127 final String value = System.getProperty(key, "");
1128 if (!value.isEmpty()) {
1129 sb.append(", ").append(value);
1130 }
1131 }
1132 return sb.append(')').append(N);
1133 }
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145 private static StringBuilder appendDate(StringBuilder sb,
1146 String prefix) {
1147
1148 final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.US);
1149 return appendPrefix(sb, prefix).append(dateFormat.format(new Date())).append(N);
1150 }
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163 private static StringBuilder appendPrefix(StringBuilder sb,
1164 String prefix) {
1165 return sb.append(C).append(prefix).append(": ");
1166 }
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194 static String bytesToString(long bytes) {
1195
1196 if (bytes < ONE_THOUSAND) {
1197 return bytes + " " + SI_UNITS[0];
1198 }
1199
1200 final int exponent = (int) (Math.log(bytes) / Math.log(SI_UNIT_BASE));
1201 final String unit = SI_UNITS[exponent];
1202 return String.format(Locale.US, "%.1f %s", bytes / Math.pow(SI_UNIT_BASE, exponent), unit);
1203 }
1204
1205
1206
1207
1208
1209
1210
1211 static int log2(long x) {
1212 return 63 - Long.numberOfLeadingZeros(x);
1213 }
1214 }
1215 }