Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-63495] Add support for general heap dumping to the compiler. #10989

Merged
merged 1 commit into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package jdk.graal.compiler.libgraal;

import java.io.Closeable;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
Expand Down Expand Up @@ -160,6 +161,11 @@ public void processReferences() {
LibGraalRuntime.processReferences();
}

@Override
public void dumpHeap(String outputFile, boolean live) throws IOException {
VMRuntime.dumpHeap(outputFile, live);
}

@Override
public long getIsolateAddress() {
return CurrentIsolate.getIsolate().rawValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,22 @@

import static jdk.graal.compiler.serviceprovider.GraalServices.getCurrentThreadId;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import com.sun.management.HotSpotDiagnosticMXBean;
import jdk.graal.compiler.serviceprovider.JMXService;
import jdk.graal.compiler.serviceprovider.ServiceProvider;

import com.sun.management.ThreadMXBean;

import javax.management.MBeanServer;

/**
* Implementation of {@link JMXService} for JDK 11+.
* Implementation of {@link JMXService}.
*/
@ServiceProvider(JMXService.class)
public class JMXServiceProvider extends JMXService {
Expand Down Expand Up @@ -66,4 +72,49 @@ protected boolean isCurrentThreadCpuTimeSupported() {
protected List<String> getInputArguments() {
return ManagementFactory.getRuntimeMXBean().getInputArguments();
}

/**
* Name of the HotSpot Diagnostic MBean.
*/
private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";

private volatile HotSpotDiagnosticMXBean hotspotMXBean;

@Override
protected void dumpHeap(String outputFile, boolean live) throws IOException {
initHotSpotMXBean();
try {
Path path = Path.of(outputFile);
if (Files.exists(path) && Files.size(path) == 0) {
Files.delete(path);
}
hotspotMXBean.dumpHeap(outputFile, live);
} catch (RuntimeException re) {
throw re;
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}

private void initHotSpotMXBean() {
if (hotspotMXBean == null) {
synchronized (this) {
if (hotspotMXBean == null) {
hotspotMXBean = getHotSpotMXBean();
}
}
}
}

private static HotSpotDiagnosticMXBean getHotSpotMXBean() {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
return ManagementFactory.newPlatformMXBeanProxy(server,
HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class);
} catch (RuntimeException re) {
throw re;
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import jdk.graal.compiler.core.GraalCompilerOptions;
import org.graalvm.collections.EconomicMap;
import org.junit.Test;

Expand All @@ -53,33 +56,49 @@ public static Object snippet() {
public void testDump() throws Exception {
assumeManagementLibraryIsLoadable();
try (TemporaryDirectory temp = new TemporaryDirectory("DumpPathTest")) {
String[] extensions = new String[]{".cfg", ".bgv", ".graph-strings"};
String[] extensions = {".cfg", ".bgv", ".graph-strings"};
EconomicMap<OptionKey<?>, Object> overrides = OptionValues.newOptionMap();
overrides.put(DebugOptions.DumpPath, temp.toString());
overrides.put(DebugOptions.ShowDumpFiles, false);
overrides.put(DebugOptions.PrintBackendCFG, true);
overrides.put(DebugOptions.PrintGraph, PrintGraphTarget.File);
overrides.put(DebugOptions.PrintCanonicalGraphStrings, true);
overrides.put(DebugOptions.Dump, "*");
overrides.put(GraalCompilerOptions.DumpHeapAfter, "<compilation>:Schedule");
overrides.put(DebugOptions.MethodFilter, null);

try (AutoCloseable c = new TTY.Filter()) {
// Generate dump files.
test(new OptionValues(getInitialOptions(), overrides), "snippet");
}
// Check that IGV files got created, in the right place.
checkForFiles(temp.path, extensions);
List<Path> paths = checkForFiles(temp.path, extensions);
List<Path> compilationHeapDumps = new ArrayList<>();
List<Path> phaseHeapDumps = new ArrayList<>();
for (Path path : paths) {
String name = path.toString();
if (name.endsWith(".compilation.hprof")) {
compilationHeapDumps.add(path);
} else if (name.endsWith(".hprof")) {
phaseHeapDumps.add(path);
}
}

assertTrue(!compilationHeapDumps.isEmpty());
assertTrue(!phaseHeapDumps.isEmpty());
}
}

/**
* Check that the given directory contains file or directory names with all the given
* extensions.
*/
private static void checkForFiles(Path directoryPath, String[] extensions) throws IOException {
private static List<Path> checkForFiles(Path directoryPath, String[] extensions) throws IOException {
String[] paths = new String[extensions.length];
List<Path> result = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directoryPath)) {
for (Path filePath : stream) {
result.add(filePath);
String fileName = filePath.getFileName().toString();
for (int i = 0; i < extensions.length; i++) {
String extension = extensions[i];
Expand All @@ -97,5 +116,6 @@ private static void checkForFiles(Path directoryPath, String[] extensions) throw
for (int i = 1; i < paths.length; i++) {
assertTrue(paths[0].equals(paths[i]), paths[0] + " != " + paths[i]);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*/
package jdk.graal.compiler.core;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import jdk.graal.compiler.code.CompilationResult;
Expand All @@ -35,8 +36,8 @@
import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.debug.DebugContext.CompilerPhaseScope;
import jdk.graal.compiler.debug.DebugOptions;
import jdk.graal.compiler.debug.GraphFilter;
import jdk.graal.compiler.debug.MemUseTrackerKey;
import jdk.graal.compiler.debug.MethodFilter;
import jdk.graal.compiler.debug.TTY;
import jdk.graal.compiler.debug.TimerKey;
import jdk.graal.compiler.lir.asm.CompilationResultBuilderFactory;
Expand All @@ -53,6 +54,7 @@
import jdk.graal.compiler.phases.tiers.Suites;
import jdk.graal.compiler.phases.tiers.TargetProvider;
import jdk.graal.compiler.phases.util.Providers;
import jdk.graal.compiler.serviceprovider.GraalServices;
import jdk.vm.ci.meta.ProfilingInfo;
import jdk.vm.ci.meta.ResolvedJavaMethod;

Expand Down Expand Up @@ -157,10 +159,28 @@ public static <T extends CompilationResult> T compile(Request<T> r) {
throw debug.handle(e);
}
checkForRequestedDelay(r.graph);

checkForHeapDump(r, debug);

return r.compilationResult;
}
}

/**
* Checks if {@link GraalCompilerOptions#DumpHeapAfter} is enabled for the compilation in
* {@code request} and if so, dumps the heap to a file specified by the debug context.
*/
private static <T extends CompilationResult> void checkForHeapDump(Request<T> request, DebugContext debug) {
if (GraalCompilerOptions.DumpHeapAfter.matches(debug.getOptions(), null, request.graph)) {
try {
final String path = debug.getDumpPath(".compilation.hprof", false);
GraalServices.dumpHeap(path, false);
} catch (IOException e) {
e.printStackTrace(System.out);
}
}
}

/**
* Support for extra processing of a crash triggered by {@link GraalCompilerOptions#CrashAt}.
*/
Expand Down Expand Up @@ -239,18 +259,7 @@ private static String match(StructuredGraph graph, String methodPattern) {
// Absence of methodPattern means match everything
return graph.name != null ? graph.name : graph.method().format("%H.%n(%p)");
}
String label = null;
if (graph.name != null && graph.name.contains(methodPattern)) {
label = graph.name;
}
if (label == null) {
ResolvedJavaMethod method = graph.method();
MethodFilter filter = MethodFilter.parse(methodPattern);
if (filter.matches(method)) {
label = method.format("%H.%n(%p)");
}
}
return label;
return new GraphFilter(methodPattern).matchedLabel(graph);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import jdk.graal.compiler.options.OptionKey;
import jdk.graal.compiler.options.OptionStability;
import jdk.graal.compiler.options.OptionType;
import jdk.graal.compiler.phases.PhaseFilterKey;

/**
* Options related to {@link GraalCompiler}.
Expand All @@ -46,6 +47,15 @@ Pattern for method(s) that will trigger an exception when compiled.
suffix will raise a bailout exception and a ':PermanentBailout'
suffix will raise a permanent bailout exception.""", type = OptionType.Debug)
public static final OptionKey<String> CrashAt = new OptionKey<>(null);
@Option(help = """
Emit a heap dump after each phase matching the given phase filter(s).

Use DumpPath or ShowDumpFiles to set or see where dumps are written.

The special phase name "<compilation>" means dump after compilation
instead of after any specific phase.
""" + PhaseFilterKey.HELP, type = OptionType.Debug)//
public static final PhaseFilterKey DumpHeapAfter = new PhaseFilterKey(null, "<compilation>");
@Option(help = "Treats compilation bailouts as compilation failures.", type = OptionType.User, stability = OptionStability.STABLE)
public static final OptionKey<Boolean> CompilationBailoutAsFailure = new OptionKey<>(false);
@Option(help = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import org.graalvm.collections.EconomicMap;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand Down Expand Up @@ -112,6 +113,15 @@ public interface LibGraalSupport {
*/
void processReferences();

/**
* Dumps the heap to {@code outputFile} in hprof format.
*
* @param live if true, performs a full GC first so that only live objects are dumped
* @throws IOException if an IO error occurred dyring dumping
* @throws UnsupportedOperationException if this operation is not supported.
*/
void dumpHeap(String outputFile, boolean live) throws IOException;

/**
* Gets the address of the current isolate.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,78 +182,7 @@ public enum OptimizationLogTarget {
@Option(help = "Pattern for specifying scopes in which logging is enabled. " +
"See the Dump option for the pattern syntax.", type = OptionType.Debug)
public static final OptionKey<String> Log = new OptionKey<>(null);
@Option(help = """
Pattern for matching methods.
The syntax for a pattern is:

SourcePatterns = SourcePattern ["," SourcePatterns] .
SourcePattern = [ "~" ] [ Class "." ] method [ "(" [ Parameter { ";" Parameter } ] ")" ] .
Parameter = Class | "int" | "long" | "float" | "double" | "short" | "char" | "boolean" .
Class = { package "." } class .

Glob pattern matching (*, ?) is allowed in all parts of the source pattern.
The "~" prefix negates the pattern.

Positive patterns are joined by an "or" operator: "A,B" matches anything
matched by "A" or "B". Negative patterns are joined by "and not": "~A,~B"
matches anything not matched by "A" and not matched by "B". "A,~B,~C,D"
matches anything matched by "A" or "D" and not matched by "B" and not
matched by "C".

A set of patterns containing negative patterns but no positive ones contains
an implicit positive "*" pattern: "~A,~B" is equivalent to "*,~A,~B".

Examples of method filters:
---------
*

Matches all methods in all classes.
---------
canonical(CanonicalizerTool;LogicNode;LogicNode)

Matches all methods named "canonical", with the first parameter of type
"CanonicalizerTool", and the second and third parameters of type
"LogicNode".
The packages of the parameter types are irrelevant.
---------
arraycopy(Object;;;;)

Matches all methods named "arraycopy", with the first parameter
of type "Object", and four more parameters of any type. The
packages of the parameter types are irrelevant.
---------
List.set

Matches all methods named "set" in a class whose simple name is "List".
---------
*List.set

Matches all methods named "set" in a class whose simple name ends with "List".
---------
jdk.graal.compiler.nodes.PhiNode.*

Matches all methods in the class "jdk.graal.compiler.nodes.PhiNode".
---------
jdk.graal.compiler.nodes.*.canonical

Matches all methods named "canonical" in classes in the package
"jdk.graal.compiler.nodes".
---------
arraycopy,toString

Matches all methods named "arraycopy" or "toString", meaning that ',' acts
as an "or" operator.
---------
java.util.*.*.,~java.util.*Array*.*
java.util.*.*.,~*Array*.*

These patterns are equivalent and match all methods in the package
"java.util" except for classes that have "Array" in their name.
---------
~java.util.*.*

Matches all methods in all classes in all packages except for anything in
the "java.util" package.""")
@Option(help = jdk.graal.compiler.debug.MethodFilter.HELP)
public static final OptionKey<String> MethodFilter = new OptionKey<>(null);
@Option(help = "Only check MethodFilter against the root method in the context if true, otherwise check all methods", type = OptionType.Debug)
public static final OptionKey<Boolean> MethodFilterRootOnly = new OptionKey<>(false);
Expand Down
Loading
Loading