diff --git a/jcstress-core/src/main/java/org/openjdk/jcstress/JCStress.java b/jcstress-core/src/main/java/org/openjdk/jcstress/JCStress.java index 37ac9e9b..e8d6e6c0 100644 --- a/jcstress-core/src/main/java/org/openjdk/jcstress/JCStress.java +++ b/jcstress-core/src/main/java/org/openjdk/jcstress/JCStress.java @@ -85,7 +85,7 @@ public void run() throws Exception { parseResults(); } - private ConfigsWithScheduler getConfigs() { + ConfigsWithScheduler getConfigs() { OSSupport.init(); VMSupport.initFlags(opts); @@ -119,7 +119,7 @@ private ConfigsWithScheduler getConfigs() { return new ConfigsWithScheduler(scheduler, configs); } - private static class ConfigsWithScheduler { + static class ConfigsWithScheduler { public final Scheduler scheduler; public final List configs; @@ -216,8 +216,11 @@ private void forkedUnified(List testConfigs, VMSupport.Config config public SortedSet getTests() { String filter = opts.getTestFilter(); - SortedSet s = new TreeSet<>(); + return getTests(filter); + } + public SortedSet getTests(String filter) { + SortedSet s = new TreeSet<>(); Pattern pattern = Pattern.compile(filter); for (String testName : TestList.tests()) { if (pattern.matcher(testName).find()) { @@ -227,25 +230,4 @@ public SortedSet getTests() { return s; } - public int listTests(Options opts) { - JCStress.ConfigsWithScheduler configsWithScheduler = getConfigs(); - Set testsToPrint = new TreeSet<>(); - for (TestConfig test : configsWithScheduler.configs) { - if (opts.verbosity().printAllTests()) { - testsToPrint.add(test.toDetailedTest()); - } else { - testsToPrint.add(test.name); - } - } - if (opts.verbosity().printAllTests()) { - out.println("All matching tests combinations - " + testsToPrint.size()); - } else { - out.println("All matching tests - " + testsToPrint.size()); - } - for (String test : testsToPrint) { - out.println(test); - } - return testsToPrint.size(); - } - } diff --git a/jcstress-core/src/main/java/org/openjdk/jcstress/Main.java b/jcstress-core/src/main/java/org/openjdk/jcstress/Main.java index 8ba123ab..f1e8c933 100644 --- a/jcstress-core/src/main/java/org/openjdk/jcstress/Main.java +++ b/jcstress-core/src/main/java/org/openjdk/jcstress/Main.java @@ -50,7 +50,8 @@ public static void main(String[] args) throws Exception { JCStress jcstress = new JCStress(opts); if (opts.shouldList()) { - jcstress.listTests(opts); + TestListing testListing = new TestListing(jcstress); + testListing.listTests(); } else if (opts.shouldParse()) { jcstress.parseResults(); } else { diff --git a/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java b/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java index 8f00bd13..e9e20bd1 100644 --- a/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java +++ b/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java @@ -52,7 +52,7 @@ public class Options { private int strideCount; private final String[] args; private boolean parse; - private boolean list; + private TestListing.ListingTypes list = TestListing.ListingTypes.NONE; private Verbosity verbosity; private int cpuCount; private int heapPerFork; @@ -81,9 +81,9 @@ public boolean parse() throws IOException { OptionSpec parse = parser.accepts("p", "Re-run parser on the result file. This will not run any tests.") .withRequiredArg().ofType(String.class).describedAs("result file"); - OptionSpec list = parser.accepts("l", "List the available tests matching the requested settings, " + - "after all filters (like CPU count) are applied. In verbose mode it prints all real combinations which will run.") - .withOptionalArg().ofType(Boolean.class).describedAs("bool"); + OptionSpec list = parser.accepts("l", "List the available tests. " + + TestListing.ListingTypes.toDescription()) + .withOptionalArg().ofType(TestListing.ListingTypes.class).describedAs("ListingTypes"); OptionSpec testFilter = parser.accepts("t", "Regexp selector for tests.") .withRequiredArg().ofType(String.class).describedAs("regexp"); @@ -184,7 +184,7 @@ public boolean parse() throws IOException { String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.ROOT).format(new Date()); this.resultFile = "jcstress-results-" + timestamp + ".bin.gz"; } - this.list = orDefault(set.has(list), false); + this.list = getList(set, list); if (set.has("vvv")) { this.verbosity = new Verbosity(3); } else if (set.has("vv")) { @@ -263,6 +263,18 @@ public boolean parse() throws IOException { return true; } + private TestListing.ListingTypes getList(OptionSet set, OptionSpec listSpec) { + if (set.has(listSpec)) { + if (set.hasArgument(listSpec)) { + return set.valueOf(listSpec); + } else { + return TestListing.ListingTypes.ALL_MATCHING; + } + } else { + return TestListing.ListingTypes.NONE; + } + } + private List processArgs(OptionSpec op, OptionSet set) { if (set.hasArgument(op)) { try { @@ -297,7 +309,7 @@ public void printSettingsOn(PrintStream out) { out.printf(" Hardware CPUs in use: %d%n", getCPUCount()); out.printf(" Spinning style: %s%n", getSpinStyle()); out.printf(" Test selection: \"%s\"%n", getTestFilter()); - out.printf(" Forks per test: %d normal, %d stress%n", getForks(), getForks()*getForksStressMultiplier()); + out.printf(" Forks per test: %d normal, %d stress%n", getForks(), getForks() * getForksStressMultiplier()); out.printf(" Test stride: %d strides x %d tests, but taking no more than %d Mb%n", getStrideCount(), getStrideSize(), getMaxFootprintMb()); out.printf(" Test result blob: \"%s\"%n", resultFile); out.printf(" Test results: \"%s\"%n", resultDir); @@ -338,6 +350,10 @@ public boolean shouldParse() { } public boolean shouldList() { + return list != TestListing.ListingTypes.NONE; + } + + public TestListing.ListingTypes listingType() { return list; } @@ -392,6 +408,8 @@ public boolean isPretouchHeap() { return pretouchHeap; } - public TimeValue timeBudget() { return timeBudget; } + public TimeValue timeBudget() { + return timeBudget; + } } diff --git a/jcstress-core/src/main/java/org/openjdk/jcstress/TestListing.java b/jcstress-core/src/main/java/org/openjdk/jcstress/TestListing.java new file mode 100644 index 00000000..d68f8df2 --- /dev/null +++ b/jcstress-core/src/main/java/org/openjdk/jcstress/TestListing.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + + */ +package org.openjdk.jcstress; + +import org.openjdk.jcstress.infra.runners.TestConfig; +import org.openjdk.jcstress.util.StringUtils; + +import java.io.PrintStream; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +public class TestListing { + + //Will flatten the header of complex json headers, if it is undesired by user for some reason + public static final String FLAT_JSON_VARIANTS = "jcstress.list.json.flat"; + //with json mixed to stdout, onehave to sed the header off, which may be inconvenient. This property will move it to stderr + //it may be better to move all the diagnostics to stderr, but that would bw quite invasive touch + //jcstress is strongly redirecting all stderrs of nested vms to stdout, so the list/json should remain clear + public static final String LIST_TO_STDERR = "jcstress.list.stderr"; + + public enum ListingTypes { + NONE, ALL, ALL_MATCHING, ALL_MATCHING_COMBINATIONS, + MATCHING_GROUPS, MATCHING_GROUPS_COUNT, + MATCHING_IGROUPS, MATCHING_IGROUPS_COUNT, + TOTAL_ALL, TOTAL_ALL_MATCHING, TOTAL_ALL_MATCHING_COMBINATIONS, + TOTAL_MATCHING_GROUPS, TOTAL_MATCHING_GROUPS_COUNT, + TOTAL_MATCHING_IGROUPS, TOTAL_MATCHING_IGROUPS_COUNT, + JSON_ALL, JSON_ALL_MATCHING, JSON_ALL_MATCHING_COMBINATIONS, + JSON_MATCHING_GROUPS, JSON_MATCHING_GROUPS_COUNT, + JSON_MATCHING_IGROUPS, JSON_MATCHING_IGROUPS_COUNT; + + public static String toDescription() { + return "Optional parameter is: " + + ALL + " all tests; " + + ALL_MATCHING + " all tests eligible for this system and configuration (like CPU count); " + + ALL_MATCHING_COMBINATIONS + " all real combinations which will run in this setup; " + + MATCHING_GROUPS + " similar to above but the shared part is printed only once; " + + MATCHING_GROUPS_COUNT + " same as above, only instead of lsiting, just count is used; " + + MATCHING_IGROUPS + ", " + MATCHING_IGROUPS_COUNT + " same as above, only inverted; " + + "Defaults to " + ALL_MATCHING + " if none provided. You can prefix by TOTAL_ or JSON_" + + "to print only summary line or to print valid jsons"; + } + } + + private final PrintStream listingOut; + private final JCStress jcstress; + + public TestListing(JCStress jcstress) { + this.jcstress = jcstress; + if (isStderr()) { + this.listingOut = System.err; + } else { + this.listingOut = jcstress.out; + } + } + + private boolean isJsonHeader() { + if (jcstress.opts.listingType() == ListingTypes.JSON_ALL || jcstress.opts.listingType() == ListingTypes.JSON_ALL_MATCHING) { + return false; + } + return System.getProperty(FLAT_JSON_VARIANTS) == null; + } + + private boolean isStderr() { + return System.getProperty(LIST_TO_STDERR) != null; + } + + @SuppressWarnings("unchecked") + public int listTests() { + Map testsToPrint = gatherTests(); + if (jcstress.opts.listingType().toString().startsWith("TOTAL_")) { + return testsToPrint.size(); + } else { + printTests(testsToPrint); + return testsToPrint.size(); + } + } + + @SuppressWarnings("unchecked") + private Map gatherTests() { + JCStress.ConfigsWithScheduler configsWithScheduler = jcstress.getConfigs(); + Map testsToPrint = new TreeMap<>(); + switch (jcstress.opts.listingType()) { + case ALL_MATCHING_COMBINATIONS: + case TOTAL_ALL_MATCHING_COMBINATIONS: + case JSON_ALL_MATCHING_COMBINATIONS: + for (TestConfig test : configsWithScheduler.configs) { + String id = test.toDetailedTest(isJsonWithHeader(), true, true, true); + testsToPrint.put(id, null); + } + jcstress.out.println("All matching tests combinations - " + testsToPrint.size()); + break; + case MATCHING_GROUPS_COUNT: + case TOTAL_MATCHING_GROUPS_COUNT: + case JSON_MATCHING_GROUPS_COUNT: + for (TestConfig test : configsWithScheduler.configs) { + String id = test.toDetailedTest(isJsonWithHeader(), false, false, isJsonWithHeader()); + Integer counter = (Integer) testsToPrint.getOrDefault(id, 0); + counter++; + testsToPrint.put(id, counter); + } + jcstress.out.println("All existing combinations (each with count of test) " + testsToPrint.size()); + break; + case MATCHING_IGROUPS_COUNT: + case TOTAL_MATCHING_IGROUPS_COUNT: + case JSON_MATCHING_IGROUPS_COUNT: + for (TestConfig test : configsWithScheduler.configs) { + String id = StringUtils.fieldToString("name", isJson(), false, test.name); + Integer counter = (Integer) testsToPrint.getOrDefault(id, 0); + counter++; + testsToPrint.put(id, counter); + } + jcstress.out.println("All matching tests (each with count of combinations) " + testsToPrint.size()); + break; + case MATCHING_GROUPS: + case TOTAL_MATCHING_GROUPS: + case JSON_MATCHING_GROUPS: + for (TestConfig test : configsWithScheduler.configs) { + String id = test.toDetailedTest(isJsonWithHeader(), false, false, isJsonWithHeader()); + Set items = (Set) testsToPrint.getOrDefault(id, new TreeSet()); + items.add(test.name); + testsToPrint.put(id, items); + } + jcstress.out.println("All existing combinations " + testsToPrint.size()); + break; + case MATCHING_IGROUPS: + case TOTAL_MATCHING_IGROUPS: + case JSON_MATCHING_IGROUPS: + for (TestConfig test : configsWithScheduler.configs) { + String id = StringUtils.fieldToString("name", isJson(), false, test.name); + Set items = (Set) (testsToPrint.getOrDefault(id, new TreeSet())); + items.add(test.toDetailedTest(isJsonWithHeader(), false, false, isJsonWithHeader())); + testsToPrint.put(id, items); + } + jcstress.out.println("All matching tests " + testsToPrint.size()); + break; + case ALL_MATCHING: + case TOTAL_ALL_MATCHING: + case JSON_ALL_MATCHING: + for (TestConfig test : configsWithScheduler.configs) { + testsToPrint.put(test.name, null); + } + jcstress.out.println("All matching tests - " + testsToPrint.size()); + break; + case ALL: + case TOTAL_ALL: + case JSON_ALL: + for (String test : jcstress.getTests(".*")) { + testsToPrint.put(test, null); + } + jcstress.out.println("All existing tests combinations - " + testsToPrint.size()); + break; + default: + throw new RuntimeException("Invalid option for listing: " + jcstress.opts.listingType()); + } + return testsToPrint; + } + + private void printTests(Map testsToPrint) { + if (isJson()) { + listingOut.println("{"); + listingOut.println("\"toal\": " + testsToPrint.size() + ", \"list\": ["); + } + Set> entries = testsToPrint.entrySet(); + int counter = entries.size(); + for (Map.Entry test : entries) { + counter--; + if (test.getValue() == null) { + if (isJson()) { + if (isJsonHeader()) { + listingOut.println("{" + test.getKey() + "}"); + } else { + listingOut.print("\"" + test.getKey() + "\""); + } + jsonArrayDelimiter(counter); + } else { + listingOut.println(test.getKey()); + } + } else { + if (test.getValue() instanceof Integer) { + if (isJson()) { + if (isJsonHeader() || isInvertedGroup()) { + listingOut.println("{" + test.getKey() + ", \"testcount\":" + test.getValue() + "}"); + } else { + listingOut.println("{\"" + test.getKey() + "\": " + test.getValue() + "}"); + } + jsonArrayDelimiter(counter); + } else { + listingOut.println(test.getValue() + " " + test.getKey()); + } + } else if (test.getValue() instanceof Collection) { + if (isJson()) { + if (isJsonHeader() || isInvertedGroup()) { + listingOut.println("{" + test.getKey() + ", \"testlist\": ["); + } else { + listingOut.println("{\"" + test.getKey() + "\": ["); + } + int subcounter = ((Collection) test.getValue()).size(); + for (Object item : (Collection) test.getValue()) { + subcounter--; + if (jcstress.opts.listingType() == ListingTypes.JSON_MATCHING_GROUPS) { + //? + listingOut.println("\"" + item + "\""); + } else if (isJsonHeader()) { + listingOut.println("{" + item + "}"); + } else { + listingOut.println("\"" + item + "\""); + } + jsonArrayDelimiter(subcounter); + } + listingOut.println("]}"); + jsonArrayDelimiter(counter); + } else { + listingOut.println(test.getKey() + " " + ((Collection) test.getValue()).size()); + for (Object item : (Collection) test.getValue()) { + listingOut.println(" " + item); + } + } + } else { + listingOut.println(test.getKey() + "=?=" + test.getValue()); + } + } + } + if (isJson()) { + listingOut.println("]}"); + } + } + + private boolean isJsonWithHeader() { + return isJson() && isJsonHeader(); + } + + private boolean isJson() { + return jcstress.opts.listingType().toString().startsWith("JSON_"); + } + + private boolean isInvertedGroup() { + return jcstress.opts.listingType().toString().contains("IGROUPS"); + } + + + private void jsonArrayDelimiter(int counter) { + if (counter != 0) { + listingOut.println(","); + } else { + listingOut.println(); + } + } + +} diff --git a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java index 079feabb..f3e3f66b 100644 --- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java +++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java @@ -30,12 +30,16 @@ import org.openjdk.jcstress.Options; import org.openjdk.jcstress.infra.TestInfo; import org.openjdk.jcstress.os.CPUMap; +import org.openjdk.jcstress.util.StringUtils; import org.openjdk.jcstress.vm.CompileMode; import org.openjdk.jcstress.vm.VMSupport; import java.io.PrintWriter; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; public class TestConfig implements Serializable { public final SpinLoopStyle spinLoopStyle; @@ -245,25 +249,54 @@ public void generateDirectives(PrintWriter pw, Verbosity verbosity) { pw.flush(); } - - public String toDetailedTest() { + public String getTestVariant(boolean seed, boolean json) { //binaryName have correct $ instead of . in name; omitted //generatedRunnerName name with suffix (usually _Test_jcstress) omitted //super.toString() as TestConfig@hash - omitted - StringBuilder verboseOutput = new StringBuilder(name); - verboseOutput.append(" {") - .append(actorNames) - .append(", spinLoopStyle: ").append(spinLoopStyle) - .append(", threads: ").append(threads) - .append(", forkId: ").append(forkId) - .append(", maxFootprintMB: ").append(maxFootprintMB) - .append(", compileMode: ").append(compileMode) - .append(", shClass: ").append(shClass) - .append(", strideSize: ").append(strideSize) - .append(", strideCount: ").append(strideCount) - .append(", cpuMap: ").append(cpuMap) - .append(", ").append(jvmArgs) - .append("}"); + StringBuilder idString = new StringBuilder(); + idString.append(StringUtils.fieldToString("actorNames", json, actorNames)) + .append(", ").append(StringUtils.fieldToString("spinLoopStyle", json, spinLoopStyle)) + .append(", ").append(StringUtils.fieldToString("threads", json, threads)) + .append(", ").append(StringUtils.fieldToString("forkId", json, forkId)) + .append(", ").append(StringUtils.fieldToString("maxFootprintMB", json, maxFootprintMB)) + .append(", ").append(StringUtils.fieldToString("compileMode", json, compileMode)) + .append(", ").append(StringUtils.fieldToString("shClass", json, shClass)) + .append(", ").append(StringUtils.fieldToString("strideSize", json, strideSize)) + .append(", ").append(StringUtils.fieldToString("strideCount", json, strideCount)) + .append(", ").append(StringUtils.fieldToString("cpuMap", json, cpuMap)) + .append(", ").append(StringUtils.fieldToString("jvmArgs", json, (seed ? jvmArgs : maskSeed(jvmArgs)))); + return idString.toString(); + } + + private List maskSeed(List jvmArgs) { + List argsCopy = new ArrayList<>(jvmArgs.size()); + for (String arg : jvmArgs) { + if (arg.startsWith("-XX:StressSeed=")) { + argsCopy.add(arg.replaceAll("[0-9]+", "yyyyyyyy")); + } else { + argsCopy.add(arg); + } + } + return argsCopy; + } + + public String toDetailedTest(boolean json, boolean showName, boolean seed, boolean keepBrackets) { + StringBuilder verboseOutput = showName ? new StringBuilder(StringUtils.fieldToString("name", json, false, name)) : new StringBuilder(); + if (json) { + if (showName) { + verboseOutput.append(", "); + } + verboseOutput.append("\"metadata\": "); + } else { + if (showName) { + verboseOutput.append(" "); + } + } + if (keepBrackets) { + verboseOutput.append("{").append(getTestVariant(seed, json)).append("}"); + } else { + verboseOutput.append(getTestVariant(seed, json)); + } return verboseOutput.toString(); } } diff --git a/jcstress-core/src/main/java/org/openjdk/jcstress/util/StringUtils.java b/jcstress-core/src/main/java/org/openjdk/jcstress/util/StringUtils.java index 1a13d06d..6c54c3bb 100644 --- a/jcstress-core/src/main/java/org/openjdk/jcstress/util/StringUtils.java +++ b/jcstress-core/src/main/java/org/openjdk/jcstress/util/StringUtils.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; public class StringUtils { @@ -173,4 +174,53 @@ public static String percent(long v, long total, int prec) { return String.format("%." + prec + "f%%", p); } } + + public static String fieldToString(String name, boolean json, Collection field) { + String value; + if (json) { + value = "\"" + (field.stream().map( a -> String.valueOf(a)).collect(Collectors.joining("\",\""))) + "\""; + } else { + value = field.toString(); + } + return fieldToStringImpl(name, json, false, value, true); + } + + public static String fieldToString(String name, boolean json, Object field) { + return fieldToString(name, json, true, String.valueOf(field)); + } + + public static String fieldToString(String name, boolean json, boolean showName, Object field) { + return fieldToStringImpl(name, json, showName, String.valueOf(field), false); + } + + private static String fieldToStringImpl(String name, boolean json, boolean showName, String field, boolean arrayQuotes) { + String finalForm; + if (showName || json) { + finalForm = key(json, name); + } else { + finalForm = ""; + } + if (json) { + if (arrayQuotes) { + return finalForm + "[" + field + "]"; + } else { + if (field.chars().allMatch( Character::isDigit )) { + //all known usages use only int as numbers + return finalForm + field; + } else { + return finalForm + "\"" + field + "\""; + } + } + } else { + return finalForm + field; + } + } + + private static String key(boolean json, String title) { + if (json) { + return "\"" + title + "\": "; + } else { + return title + ": "; + } + } }