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