Skip to content

Commit b248166

Browse files
committed
added TimeLimit and used it in DumpPathTest
1 parent a4e3a8b commit b248166

File tree

3 files changed

+220
-30
lines changed

3 files changed

+220
-30
lines changed

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/core/test/DumpPathTest.java

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
import java.util.ArrayList;
3232
import java.util.List;
3333

34-
import jdk.graal.compiler.core.GraalCompilerOptions;
3534
import org.graalvm.collections.EconomicMap;
3635
import org.junit.Test;
3736

37+
import jdk.graal.compiler.core.GraalCompilerOptions;
3838
import jdk.graal.compiler.debug.DebugOptions;
3939
import jdk.graal.compiler.debug.DebugOptions.PrintGraphTarget;
4040
import jdk.graal.compiler.debug.TTY;
@@ -47,44 +47,52 @@
4747
*/
4848
public class DumpPathTest extends GraalCompilerTest {
4949

50+
/**
51+
* If this test does not complete in 60 seconds, something is very wrong.
52+
*/
53+
private static final int TIME_LIMIT = 60000;
54+
5055
public static Object snippet() {
5156
return new String("snippet");
5257
}
5358

5459
@Test
5560
public void testDump() throws Exception {
5661
assumeManagementLibraryIsLoadable();
57-
try (TemporaryDirectory temp = new TemporaryDirectory("DumpPathTest")) {
58-
String[] extensions = {".cfg", ".bgv", ".graph-strings"};
59-
EconomicMap<OptionKey<?>, Object> overrides = OptionValues.newOptionMap();
60-
overrides.put(DebugOptions.DumpPath, temp.toString());
61-
overrides.put(DebugOptions.ShowDumpFiles, false);
62-
overrides.put(DebugOptions.PrintBackendCFG, true);
63-
overrides.put(DebugOptions.PrintGraph, PrintGraphTarget.File);
64-
overrides.put(DebugOptions.PrintCanonicalGraphStrings, true);
65-
overrides.put(DebugOptions.Dump, "*");
66-
overrides.put(GraalCompilerOptions.DumpHeapAfter, "<compilation>:Schedule");
67-
overrides.put(DebugOptions.MethodFilter, null);
62+
try (AutoCloseable _ = new TTY.Filter();
63+
TimeLimit _ = TimeLimit.create(TIME_LIMIT, "DumpPathTest")) {
64+
try (TemporaryDirectory temp = new TemporaryDirectory("DumpPathTest")) {
65+
String[] extensions = {".cfg", ".bgv", ".graph-strings"};
66+
EconomicMap<OptionKey<?>, Object> overrides = OptionValues.newOptionMap();
67+
overrides.put(DebugOptions.DumpPath, temp.toString());
68+
overrides.put(DebugOptions.ShowDumpFiles, false);
69+
overrides.put(DebugOptions.PrintBackendCFG, true);
70+
overrides.put(DebugOptions.PrintGraph, PrintGraphTarget.File);
71+
overrides.put(DebugOptions.PrintCanonicalGraphStrings, true);
72+
overrides.put(DebugOptions.Dump, "*");
73+
overrides.put(GraalCompilerOptions.DumpHeapAfter, "<compilation>:Schedule");
74+
overrides.put(DebugOptions.MethodFilter, null);
6875

69-
try (AutoCloseable _ = new TTY.Filter()) {
70-
// Generate dump files.
71-
test(new OptionValues(getInitialOptions(), overrides), "snippet");
72-
}
73-
// Check that IGV files got created, in the right place.
74-
List<Path> paths = checkForFiles(temp.path, extensions);
75-
List<Path> compilationHeapDumps = new ArrayList<>();
76-
List<Path> phaseHeapDumps = new ArrayList<>();
77-
for (Path path : paths) {
78-
String name = path.toString();
79-
if (name.endsWith(".compilation.hprof")) {
80-
compilationHeapDumps.add(path);
81-
} else if (name.endsWith(".hprof")) {
82-
phaseHeapDumps.add(path);
76+
try (AutoCloseable _ = new TTY.Filter()) {
77+
// Generate dump files.
78+
test(new OptionValues(getInitialOptions(), overrides), "snippet");
79+
}
80+
// Check that IGV files got created, in the right place.
81+
List<Path> paths = checkForFiles(temp.path, extensions);
82+
List<Path> compilationHeapDumps = new ArrayList<>();
83+
List<Path> phaseHeapDumps = new ArrayList<>();
84+
for (Path path : paths) {
85+
String name = path.toString();
86+
if (name.endsWith(".compilation.hprof")) {
87+
compilationHeapDumps.add(path);
88+
} else if (name.endsWith(".hprof")) {
89+
phaseHeapDumps.add(path);
90+
}
8391
}
84-
}
8592

86-
assertTrue(!compilationHeapDumps.isEmpty());
87-
assertTrue(!phaseHeapDumps.isEmpty());
93+
assertTrue(!compilationHeapDumps.isEmpty());
94+
assertTrue(!phaseHeapDumps.isEmpty());
95+
}
8896
}
8997
}
9098

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package jdk.graal.compiler.core.test;
26+
27+
import jdk.graal.compiler.test.GraalTest;
28+
29+
import java.lang.management.LockInfo;
30+
import java.lang.management.ManagementFactory;
31+
import java.lang.management.MonitorInfo;
32+
import java.lang.management.ThreadInfo;
33+
import java.util.Date;
34+
import java.util.Formatter;
35+
import java.util.concurrent.TimeUnit;
36+
37+
/**
38+
* A try-with-resources scope for raising an error if the time spent executing in the scope exceeds
39+
* some limit.
40+
*
41+
* {@snippet lang = java :
42+
* try (var scope = TimeLimit.create(1000, "MyTask")) {
43+
* // MyTask must complete in under 1000 milliseconds to
44+
* // avoid AssertionError being thrown when scope is closed.
45+
* work();
46+
* }
47+
* }
48+
*/
49+
public final class TimeLimit implements AutoCloseable {
50+
51+
private volatile boolean closed;
52+
private final Thread watchdog;
53+
54+
/**
55+
* Describes error to thrown when {@link #close()} is called.
56+
*/
57+
private String error;
58+
59+
/**
60+
* Enters a scope for timing execution within the scope.
61+
*
62+
* @param limit a time limit in milliseconds. If the execution exceeds this limit, an
63+
* {@link AssertionError} is thrown when the scope is {@linkplain #close() closed}.
64+
* The error will include a dump of all threads and other info that should provide
65+
* insight as to why the execution ran too long.
66+
* @param label a descriptive name of the execution
67+
* @return a TimeLimit scope object or null if JMX is not available
68+
*/
69+
public static TimeLimit create(long limit, String label) {
70+
if (GraalTest.isManagementLibraryIsLoadable() != null) {
71+
return null;
72+
}
73+
return new TimeLimit(limit, label);
74+
}
75+
76+
private TimeLimit(long limit, String label) {
77+
watchdog = new Thread("TimeLimitWatchDog") {
78+
@Override
79+
public void run() {
80+
long elapsed = 0;
81+
while (elapsed < limit && !closed) {
82+
long start = System.nanoTime();
83+
try {
84+
Thread.sleep(limit - elapsed);
85+
} catch (InterruptedException e) {
86+
// ignore
87+
}
88+
elapsed += TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
89+
}
90+
if (!closed) {
91+
setError(limit, label);
92+
}
93+
}
94+
};
95+
watchdog.setDaemon(true);
96+
watchdog.start();
97+
98+
}
99+
100+
@Override
101+
public void close() {
102+
closed = true;
103+
watchdog.interrupt();
104+
if (error != null) {
105+
throw new AssertionError(error);
106+
}
107+
}
108+
109+
private void setError(long limit, String label) {
110+
Formatter buf = new Formatter();
111+
buf.format("Task '%s' failed to complete within %d milleseconds%n", label, limit);
112+
buf.format("Dumping all stack traces. Current time: %s%n", new Date());
113+
threadDump(buf);
114+
Runtime runtime = Runtime.getRuntime();
115+
final long heapSizeUnit = 1024 * 1024;
116+
long usedHeapSize = runtime.totalMemory() / heapSizeUnit;
117+
long freeHeapSize = runtime.freeMemory() / heapSizeUnit;
118+
long maximumHeapSize = runtime.maxMemory() / heapSizeUnit;
119+
buf.format("=== Memory statistics (in MB):%n=== Used heap size: %d%n=== Free heap size: %d%n=== Maximum heap size: %d%n", usedHeapSize, freeHeapSize, maximumHeapSize);
120+
this.error = buf.toString();
121+
}
122+
123+
private static void threadDump(Formatter buf) {
124+
for (ThreadInfo ti : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) {
125+
printThreadInfo(buf, ti);
126+
printLockInfo(buf, ti.getLockedSynchronizers());
127+
}
128+
buf.format("%n");
129+
}
130+
131+
private static void printThreadInfo(Formatter buf, ThreadInfo ti) {
132+
buf.format("\"%s\" Id=%d in %s", ti.getThreadName(), ti.getThreadId(), ti.getThreadState());
133+
if (ti.getLockName() != null) {
134+
buf.format(" on lock=%s", ti.getLockName());
135+
}
136+
if (ti.isSuspended()) {
137+
buf.format(" (suspended)");
138+
}
139+
if (ti.isInNative()) {
140+
buf.format(" (running in native)");
141+
}
142+
buf.format("%n");
143+
144+
if (ti.getLockOwnerName() != null) {
145+
buf.format(" owned by %s Id=%d%n", ti.getLockOwnerName(), ti.getLockOwnerId());
146+
}
147+
148+
StackTraceElement[] stacktrace = ti.getStackTrace();
149+
MonitorInfo[] monitors = ti.getLockedMonitors();
150+
for (int i = 0; i < stacktrace.length; i++) {
151+
StackTraceElement ste = stacktrace[i];
152+
buf.format(" at %s%n", ste);
153+
for (MonitorInfo mi : monitors) {
154+
if (mi.getLockedStackDepth() == i) {
155+
buf.format(" - locked %s%n", mi);
156+
}
157+
}
158+
}
159+
buf.format("%n");
160+
}
161+
162+
private static void printLockInfo(Formatter buf, LockInfo[] locks) {
163+
if (locks.length > 0) {
164+
buf.format(" Locked synchronizers: count = %d%n", locks.length);
165+
for (LockInfo li : locks) {
166+
buf.format(" - %s%n", li);
167+
}
168+
buf.format("%n");
169+
}
170+
}
171+
}

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/test/GraalTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,23 @@ protected static StructuredGraph assertInGraph(StructuredGraph graph, Class<?>..
318318
* @see "https://bugs.openjdk.java.net/browse/JDK-8076557"
319319
*/
320320
public static void assumeManagementLibraryIsLoadable() {
321+
Throwable unloadableReason = isManagementLibraryIsLoadable();
322+
if (unloadableReason != null) {
323+
throw new AssumptionViolatedException("Management interface is unavailable: " + unloadableReason);
324+
}
325+
}
326+
327+
/**
328+
* @see "https://bugs.openjdk.java.net/browse/JDK-8076557"
329+
*/
330+
public static Throwable isManagementLibraryIsLoadable() {
321331
try {
322332
/* Trigger loading of the management library using the bootstrap class loader. */
323333
GraalServices.getCurrentThreadAllocatedBytes();
324334
} catch (UnsatisfiedLinkError | NoClassDefFoundError | UnsupportedOperationException e) {
325-
throw new AssumptionViolatedException("Management interface is unavailable: " + e);
335+
return e;
326336
}
337+
return null;
327338
}
328339

329340
/**

0 commit comments

Comments
 (0)