From 771cb85c964c2c7eafc911c879683ffc334b677d Mon Sep 17 00:00:00 2001 From: Marvin Blauth Date: Thu, 11 Aug 2022 11:51:33 +0200 Subject: [PATCH] #4: Add (deactivated) functionality to write execAndSave()-dumps to a directory Since this only one piece for the directory support, it is not activated for use in the command line tool or UI. --- src/main/java/jdump/dump/Configuration.java | 35 +++++++++++++++ src/main/java/jdump/dump/HotspotDump.java | 9 ++-- src/main/java/jdump/dump/NMTDump.java | 26 ++++------- src/main/java/jdump/dump/ThreadDump.java | 11 +++-- .../java/jdump/output/DirectoryOutput.java | 45 +++++++++++++++++++ src/main/java/jdump/output/FileOutput.java | 40 +++++++++++++++++ src/main/java/jdump/output/Output.java | 26 +++++++++++ .../java/jdump/dump/DirectoryOutputTest.java | 41 +++++++++++++++++ 8 files changed, 206 insertions(+), 27 deletions(-) create mode 100644 src/main/java/jdump/output/DirectoryOutput.java create mode 100644 src/main/java/jdump/output/FileOutput.java create mode 100644 src/main/java/jdump/output/Output.java create mode 100644 src/test/java/jdump/dump/DirectoryOutputTest.java diff --git a/src/main/java/jdump/dump/Configuration.java b/src/main/java/jdump/dump/Configuration.java index ead7851..7a21cbf 100644 --- a/src/main/java/jdump/dump/Configuration.java +++ b/src/main/java/jdump/dump/Configuration.java @@ -1,5 +1,7 @@ package jdump.dump; +import jdump.output.Output; + import java.time.Duration; /** @@ -15,6 +17,7 @@ public class Configuration { private Duration jfrDuration = JFRDump.DEFAULT_JFR_DURATION; private boolean wantNmtForAll = false; private String outputDirectory = System.getProperty("user.dir"); + private Output.TYPE outputType = Output.TYPE.FILE; private Configuration() {} @@ -25,6 +28,8 @@ private Configuration(Configuration configuration) { this.jfrDuration = configuration.jfrDuration; this.wantNmtForAll = configuration.wantNmtForAll; this.outputDirectory = configuration.outputDirectory; + this.outputType = configuration.outputType == + Output.TYPE.DIRECTORY || moreThanOneDump() ? Output.TYPE.DIRECTORY : Output.TYPE.FILE; } public static class Mutable extends Configuration { @@ -60,6 +65,24 @@ public void wantAllDumps() { super.wantJfrForAll(); super.wantNmtForAll(); } + + public Mutable outputDirectory(String directoryName) { + super.outputDirectory(directoryName); + return this; + } + + public Mutable outputType(Output.TYPE outputType) { + super.outputType(outputType); + return this; + } + } + + private void outputType(Output.TYPE outputType) { + this.outputType = outputType; + } + + private void outputDirectory(String directoryName) { + this.outputDirectory = directoryName; } private void wantNmtForAll() { @@ -106,8 +129,20 @@ public Duration jfrDuration() { return jfrDuration; } + public Output.TYPE outputType() { + return outputType; + } + static Configuration defaultConfiguration() { return new Configuration(); } + private boolean moreThanOneDump() { + int numberOfDumpTypes = 0; + if (wantHeapDumpForAll) numberOfDumpTypes++; + if (wantThreadDumpForAll) numberOfDumpTypes++; + if (wantJFRForAll) numberOfDumpTypes++; + if (wantNmtForAll) numberOfDumpTypes++; + return numberOfDumpTypes > 1; + } } diff --git a/src/main/java/jdump/dump/HotspotDump.java b/src/main/java/jdump/dump/HotspotDump.java index 2cc9775..81a2fb5 100644 --- a/src/main/java/jdump/dump/HotspotDump.java +++ b/src/main/java/jdump/dump/HotspotDump.java @@ -2,9 +2,9 @@ import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachineDescriptor; +import jdump.output.Output; import sun.tools.attach.HotSpotVirtualMachine; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -38,14 +38,15 @@ void execAndPrint(VirtualMachineDescriptor vmd, String command, Object... args) /** * Executes an Attach API command and writes the returned output to a given file. * @param vmd the {@link VirtualMachineDescriptor} describing the JVM to which to send the command - * @param outputFile the file to which to write the output + * @param output the {@link Output} for persisting the dump * @param command the Attach API command to send to the JVM * @param args the Arguments for the Attach API command */ - void execAndSave(VirtualMachineDescriptor vmd, File outputFile, String command, Object... args) { + void execAndSave(VirtualMachineDescriptor vmd, Output output, String command, Object... args) { try { InputStream is = executeCommand(vmd, command, args); - Files.copy(is, outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + System.out.println("dumping to " + output.path()); + Files.copy(is, output.path(), StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { System.err.println("Failed for JVM " + vmd.id() + ": " + e); } diff --git a/src/main/java/jdump/dump/NMTDump.java b/src/main/java/jdump/dump/NMTDump.java index ac24a8b..72b2257 100644 --- a/src/main/java/jdump/dump/NMTDump.java +++ b/src/main/java/jdump/dump/NMTDump.java @@ -1,39 +1,31 @@ package jdump.dump; import com.sun.tools.attach.VirtualMachineDescriptor; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; +import jdump.output.Output; /** * Supports dumping native memory tracks. */ public class NMTDump extends HotspotDump { - private final String outputDirectory; + private final Configuration configuration; NMTDump(Configuration configuration) { - this.outputDirectory = configuration.outputDirectory(); + this.configuration = configuration; } @Override void performFor(VirtualMachineDescriptor vmd) { System.out.println("Dump NMT for JVM " + vmd.id()); - execAndSave(vmd, new File(filenameFor(vmd)), "jcmd","VM.native_memory"); - var path = Paths.get(filenameFor(vmd)); - try(var lines = Files.lines(path)) { - if (lines.anyMatch(line -> line.contains("Native memory tracking is not enabled"))) { - System.err.println("Native memory tracking is not enabled for JVM " + vmd.id()); - Files.delete(path); - } - } catch (IOException e) { - System.err.println("Failure generating NMT dump:" + e.getMessage()); + var output = Output.using(configuration); + execAndSave(vmd, output.file(filenameFor(vmd)), "jcmd","VM.native_memory"); + if (output.aLineMatches("Native memory tracking is not enabled")) { + System.err.println("Native memory tracking is not enabled for JVM " + vmd.id()); + output.deleteFile(); } } @Override String filenameFor(VirtualMachineDescriptor vmd) { - return outputDirectory + File.separator + "jdump-nmt-" + vmd.id() + ".txt"; + return "jdump-nmt-" + vmd.id() + ".txt"; } } diff --git a/src/main/java/jdump/dump/ThreadDump.java b/src/main/java/jdump/dump/ThreadDump.java index 6787c78..9331e8d 100644 --- a/src/main/java/jdump/dump/ThreadDump.java +++ b/src/main/java/jdump/dump/ThreadDump.java @@ -1,27 +1,26 @@ package jdump.dump; import com.sun.tools.attach.VirtualMachineDescriptor; - -import java.io.File; +import jdump.output.Output; /** * Supports dumping threads, i.e. stack traces of all running threads. */ class ThreadDump extends HotspotDump { - private final String outputDirectory; + private final Configuration configuration; ThreadDump(Configuration configuration) { - this.outputDirectory = configuration.outputDirectory(); + this.configuration = configuration; } @Override void performFor(VirtualMachineDescriptor vmd) { System.out.println("Dumping threads for JVM " + vmd.id()); - execAndSave(vmd, new File(filenameFor(vmd)), "threaddump"); + execAndSave(vmd, Output.using(configuration).file(filenameFor(vmd)), "threaddump"); } @Override String filenameFor(VirtualMachineDescriptor virtualMachineDescriptor) { - return outputDirectory + File.separator + "jdump-threads-" + virtualMachineDescriptor.id() + ".txt"; + return "jdump-threads-" + virtualMachineDescriptor.id() + ".txt"; } } diff --git a/src/main/java/jdump/output/DirectoryOutput.java b/src/main/java/jdump/output/DirectoryOutput.java new file mode 100644 index 0000000..c793dfa --- /dev/null +++ b/src/main/java/jdump/output/DirectoryOutput.java @@ -0,0 +1,45 @@ +package jdump.output; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class DirectoryOutput implements Output { + private final File directory; + private File currentFile; + + DirectoryOutput(String dirname) { + directory = new File(dirname); + if (!directory.mkdirs()) { + throw new RuntimeException("Could not create directory " + dirname); + } + System.out.println("created directory " + directory); + } + + @Override + public Output file(String filename) { + currentFile = new File(directory.getPath() + File.separator + filename); + return this; + } + + @Override + public Path path() { + return currentFile.toPath(); + } + + @Override + public boolean aLineMatches(String matchString) { + try (var lines = Files.lines(path())) { + return lines.anyMatch(line -> line.contains(matchString)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteFile() { + currentFile.delete(); + } + +} diff --git a/src/main/java/jdump/output/FileOutput.java b/src/main/java/jdump/output/FileOutput.java new file mode 100644 index 0000000..1bc4ada --- /dev/null +++ b/src/main/java/jdump/output/FileOutput.java @@ -0,0 +1,40 @@ +package jdump.output; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileOutput implements Output { + private File file; + private final String directoryName; + + FileOutput(String directoryName) { + this.directoryName = directoryName; + } + + @Override + public FileOutput file(String filename) { + file = new File(directoryName + File.separator + filename); + return this; + } + + @Override + public Path path() { + return file.toPath(); + } + + @Override + public boolean aLineMatches(String matchString) { + try (var lines = Files.lines(path())) { + return lines.anyMatch(line -> line.contains(matchString)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void deleteFile() { + file.delete(); + } +} diff --git a/src/main/java/jdump/output/Output.java b/src/main/java/jdump/output/Output.java new file mode 100644 index 0000000..919ca21 --- /dev/null +++ b/src/main/java/jdump/output/Output.java @@ -0,0 +1,26 @@ +package jdump.output; + +import jdump.dump.Configuration; + +import java.nio.file.Path; + +public interface Output { + Output file(String filename); + Path path(); + boolean aLineMatches(String matchString); + void deleteFile(); + + static Output using(Configuration configuration) { + switch (configuration.outputType()) { + case FILE: + return new FileOutput(configuration.outputDirectory()); + case DIRECTORY: + return new DirectoryOutput(configuration.outputDirectory()); + default: throw new RuntimeException("Internal error: could not handle output type"); + } + } + + enum TYPE { + FILE, DIRECTORY + } +} diff --git a/src/test/java/jdump/dump/DirectoryOutputTest.java b/src/test/java/jdump/dump/DirectoryOutputTest.java new file mode 100644 index 0000000..b3f88d8 --- /dev/null +++ b/src/test/java/jdump/dump/DirectoryOutputTest.java @@ -0,0 +1,41 @@ +package jdump.dump; + +import jdump.output.Output; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DirectoryOutputTest { + + @Test + public void testDirectoryOutput() throws ExecutionException, InterruptedException, TimeoutException, IOException { + try (JVM jvm = new JVM("-XX:NativeMemoryTracking=summary")) { + var directoryName = System.getProperty("user.dir") + File.separator + "foo"; + jvm.spawn(); + var dump = new NMTDump(new Configuration.Mutable() + .outputDirectory(directoryName) + .outputType(Output.TYPE.DIRECTORY) + .makeImmutable()); + dump.performFor(jvm.descriptor()); + var path = Path.of(directoryName); + assertTrue(Files.exists(path), "Folder " + directoryName + "was created"); + int filesDeleted = 0; + for (File file : Objects.requireNonNull(path.toFile().listFiles())) { + Files.delete(file.toPath()); + filesDeleted++; + } + assertEquals(1, filesDeleted, "One file was created"); + Files.delete(path); + } + } +} +