changeset 322:0eecf3f89210

7901794: Limit the @State instance counts for large @State objects to dodge OOME
author shade
date Fri, 23 Sep 2016 15:56:58 +0200
parents 85b7e7326978
children 62580530cd03
files jcstress-core/src/main/java/org/openjdk/jcstress/Options.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/processors/JCStressTestProcessor.java jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java jcstress-core/src/main/java/org/openjdk/jcstress/vm/AllocProfileMain.java jcstress-core/src/main/java/org/openjdk/jcstress/vm/AllocProfileSupport.java jcstress-core/src/main/java/org/openjdk/jcstress/vm/VMSupport.java
diffstat 6 files changed, 225 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java	Fri Sep 23 12:09:31 2016 +0200
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/Options.java	Fri Sep 23 15:56:58 2016 +0200
@@ -48,6 +48,7 @@
     private String resultDir;
     private String testFilter;
     private int minStride, maxStride;
+    private int maxFootprint;
     private int time;
     private int iters;
     private final String[] args;
@@ -106,6 +107,10 @@
                 "the autodetection.")
                 .withRequiredArg().ofType(Integer.class).describedAs("N");
 
+        OptionSpec<Integer> maxFootprint = parser.accepts("mf", "Maximum footprint for each test, in megabytes. This " +
+                "affects the stride size: maximum footprint will never be exceeded, regardless of min/max stride sizes.")
+                .withRequiredArg().ofType(Integer.class).describedAs("MB");
+
         OptionSpec<Boolean> shouldYield = parser.accepts("yield", "Call Thread.yield() in busy loops.")
                 .withOptionalArg().ofType(Boolean.class).describedAs("bool");
 
@@ -150,6 +155,7 @@
 
         this.minStride = orDefault(set.valueOf(minStride), 10);
         this.maxStride = orDefault(set.valueOf(maxStride), 10000);
+        this.maxFootprint = orDefault(set.valueOf(maxFootprint), 100);
         this.testFilter = orDefault(set.valueOf(testFilter), ".*");
         this.deoptRatio = orDefault(set.valueOf(deoptRatio), 5);
 
@@ -240,7 +246,7 @@
         out.printf("  Writing the test results to \"%s\"\n", resultFile);
         out.printf("  Parsing results to \"%s\"\n", resultDir);
         out.printf("  Running each test matching \"%s\" for %d forks, %d iterations, %d ms each\n", getTestFilter(), getForks(), getIterations(), getTime());
-        out.printf("  Solo stride size will be autobalanced within [%d, %d] elements\n", getMinStride(), getMaxStride());
+        out.printf("  Solo stride size will be autobalanced within [%d, %d] elements, but taking no more than %d Mb.\n", getMinStride(), getMaxStride(), getMaxFootprintMb());
 
         out.println();
     }
@@ -316,4 +322,8 @@
     public Collection<String> getJvmArgs() {
         return jvmArgs;
     }
+
+    public int getMaxFootprintMb() {
+        return maxFootprint;
+    }
 }
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/processors/JCStressTestProcessor.java	Fri Sep 23 12:09:31 2016 +0200
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/processors/JCStressTestProcessor.java	Fri Sep 23 15:56:58 2016 +0200
@@ -339,6 +339,11 @@
 
         pw.println("    @Override");
         pw.println("    public void sanityCheck() throws Throwable {");
+        pw.println("        sanityCheck_API();");
+        pw.println("        sanityCheck_Footprints();");
+        pw.println("    }");
+        pw.println();
+        pw.println("    private void sanityCheck_API() throws Throwable {");
         pw.println("        final " + t + " t = new " + t + "();");
         pw.println("        final " + s + " s = new " + s + "();");
         pw.println("        final " + r + " r = new " + r + "();");
@@ -361,7 +366,29 @@
         if (info.getArbiter() != null) {
             emitMethod(pw, info.getArbiter(), "        t." + info.getArbiter().getSimpleName(), "s", "r", true);
         }
+        pw.println("    }");
+        pw.println();
 
+        pw.println("    private void sanityCheck_Footprints() throws Throwable {");
+        pw.println("        config.adjustStrides(size -> {");
+        pw.println("            version = new StateHolder<>(new Pair[size], " + actorsCount + ", config.spinLoopStyle);");
+        pw.println("            final " + t + " t = new " + t + "();");
+        pw.println("            for (int c = 0; c < size; c++) {");
+        pw.println("                Pair p = new Pair();");
+        pw.println("                p.r = new " + r + "();");
+        pw.println("                p.s = new " + s + "();");
+        pw.println("                version.pairs[c] = p;");
+        for (ExecutableElement el : info.getActors()) {
+            pw.print("                ");
+            if (isStateItself) {
+                emitMethod(pw, el, "p.s." + el.getSimpleName(), "p.s", "p.r", false);
+            } else {
+                emitMethod(pw, el, "t." + el.getSimpleName(), "p.s", "p.r", false);
+            }
+            pw.println(";");
+        }
+        pw.println("            }");
+        pw.println("        });");
         pw.println("    }");
         pw.println();
 
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java	Fri Sep 23 12:09:31 2016 +0200
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/infra/runners/TestConfig.java	Fri Sep 23 15:56:58 2016 +0200
@@ -26,10 +26,12 @@
 
 import org.openjdk.jcstress.Options;
 import org.openjdk.jcstress.infra.TestInfo;
+import org.openjdk.jcstress.vm.AllocProfileSupport;
 
 import java.io.Serializable;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class TestConfig implements Serializable {
 
@@ -38,8 +40,6 @@
     public final int uniqueToken;
     public final SpinLoopStyle spinLoopStyle;
     public final boolean verbose;
-    public final int minStride;
-    public final int maxStride;
     public final int time;
     public final int iters;
     public final int deoptRatio;
@@ -49,6 +49,9 @@
     public final List<String> jvmArgs;
     public final RunMode runMode;
     public final int forkId;
+    public final int maxFootprintMB;
+    public int minStride;
+    public int maxStride;
 
     public enum RunMode {
         EMBEDDED,
@@ -67,11 +70,46 @@
         spinLoopStyle = opts.getSpinStyle();
         verbose = opts.isVerbose();
         deoptRatio = opts.deoptRatio();
+        maxFootprintMB = opts.getMaxFootprintMb();
         threads = info.threads();
         name = info.name();
         generatedRunnerName = info.generatedRunner();
     }
 
+    public void adjustStrides(Consumer<Integer> tryAllocate) {
+        int count = 1;
+        int succCount = count;
+        while (true) {
+            long start = AllocProfileSupport.getAllocatedBytes();
+            try {
+                tryAllocate.accept(count);
+                long footprint = AllocProfileSupport.getAllocatedBytes() - start;
+
+                if (footprint > maxFootprintMB * 1024 * 1024) {
+                    // blown the footprint estimate
+                    break;
+                }
+            } catch (OutOfMemoryError err) {
+                // blown the heap size
+                break;
+            }
+
+            // success!
+            succCount = count;
+
+            // do not go over the maxStride
+            if (succCount > maxStride) {
+                succCount = maxStride;
+                break;
+            }
+
+            count *= 2;
+        }
+
+        maxStride = Math.min(maxStride, succCount);
+        minStride = Math.min(minStride, succCount);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/vm/AllocProfileMain.java	Fri Sep 23 15:56:58 2016 +0200
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014, 2015, 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.vm;
+
+public class AllocProfileMain {
+
+    static Object sink;
+
+    public static void main(String... args) {
+        if (!AllocProfileSupport.isAvailable()) {
+            System.out.println("Allocation profiling is not available!");
+            System.exit(1);
+        }
+
+        for (int count : new int[] {1, 100, 10000, 1000000}) {
+            long start = AllocProfileSupport.getAllocatedBytes();
+            sink = new int[count];
+            long footprint = AllocProfileSupport.getAllocatedBytes() - start;
+
+            if ((footprint < Integer.BYTES * count) ||
+                (footprint > 1000 + Integer.BYTES * count)) {
+                System.out.println("Allocation profiling is dysfunctional: " +
+                        "new int[" + count + "] takes " + footprint + " bytes?");
+                System.exit(1);
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/vm/AllocProfileSupport.java	Fri Sep 23 15:56:58 2016 +0200
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014, 2015, 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.vm;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * This class encapsulates any platform-specific functionality. It is supposed
+ * to gracefully fail if some functionality is not available. This class
+ * resolves most special classes via Reflection to enable building against a
+ * standard JDK.
+ */
+public class AllocProfileSupport {
+    private static final boolean ALLOC_AVAILABLE;
+    private static ThreadMXBean ALLOC_MX_BEAN;
+    private static Method ALLOC_MX_BEAN_GETTER;
+
+    static {
+        ALLOC_AVAILABLE = tryInitAlloc();
+    }
+
+    private static boolean tryInitAlloc() {
+        try {
+            Class<?> internalIntf = Class.forName("com.sun.management.ThreadMXBean");
+            ThreadMXBean bean = ManagementFactory.getThreadMXBean();
+            if (!internalIntf.isAssignableFrom(bean.getClass())) {
+                Class<?> pmo = Class.forName("java.lang.management.PlatformManagedObject");
+                Method m = ManagementFactory.class.getMethod("getPlatformMXBean", Class.class, pmo);
+                bean = (ThreadMXBean) m.invoke(null, internalIntf);
+                if (bean == null) {
+                    throw new UnsupportedOperationException("No way to access private ThreadMXBean");
+                }
+            }
+
+            ALLOC_MX_BEAN = bean;
+            ALLOC_MX_BEAN_GETTER = internalIntf.getMethod("getThreadAllocatedBytes", long[].class);
+            getAllocatedBytes(bean.getAllThreadIds());
+
+            return true;
+        } catch (Throwable e) {
+            System.out.println("WARNING: Allocation profiling is not available: " + e.getMessage());
+        }
+        return false;
+    }
+
+    public static long getAllocatedBytes() {
+        long[] threadIds = {Thread.currentThread().getId()};
+        return getAllocatedBytes(threadIds)[0];
+    }
+
+    private static long[] getAllocatedBytes(long[] threadIds) {
+        if (ALLOC_AVAILABLE) {
+            try {
+                return (long[]) ALLOC_MX_BEAN_GETTER.invoke(ALLOC_MX_BEAN, (Object) threadIds);
+            } catch (InvocationTargetException | IllegalAccessException e) {
+                throw new IllegalStateException(e);
+            }
+        } else {
+            return new long[threadIds.length];
+        }
+    }
+
+    public static boolean isAvailable() {
+        return ALLOC_AVAILABLE;
+    }
+
+}
--- a/jcstress-core/src/main/java/org/openjdk/jcstress/vm/VMSupport.java	Fri Sep 23 12:09:31 2016 +0200
+++ b/jcstress-core/src/main/java/org/openjdk/jcstress/vm/VMSupport.java	Fri Sep 23 15:56:58 2016 +0200
@@ -70,6 +70,10 @@
                 "-XX:+WhiteBoxAPI",
                 DeoptTestMain.class);
 
+        detect("Testing allocation profiling",
+                "",
+                AllocProfileMain.class);
+
         THREAD_SPIN_WAIT_AVAILABLE =
                 detect("Trying Thread.onSpinWait",
                 "",