Skip to content

Commit

Permalink
#4: Add (deactivated) functionality to write execAndSave()-dumps to a…
Browse files Browse the repository at this point in the history
… directory

Since this only one piece for the directory support, it is not activated for use in the command line tool or UI.
  • Loading branch information
mblauth committed Aug 11, 2022
1 parent 67f2de0 commit 771cb85
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 27 deletions.
35 changes: 35 additions & 0 deletions src/main/java/jdump/dump/Configuration.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package jdump.dump;

import jdump.output.Output;

import java.time.Duration;

/**
Expand All @@ -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() {}

Expand All @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
}
}
9 changes: 5 additions & 4 deletions src/main/java/jdump/dump/HotspotDump.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
26 changes: 9 additions & 17 deletions src/main/java/jdump/dump/NMTDump.java
Original file line number Diff line number Diff line change
@@ -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";
}
}
11 changes: 5 additions & 6 deletions src/main/java/jdump/dump/ThreadDump.java
Original file line number Diff line number Diff line change
@@ -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";
}
}
45 changes: 45 additions & 0 deletions src/main/java/jdump/output/DirectoryOutput.java
Original file line number Diff line number Diff line change
@@ -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();
}

}
40 changes: 40 additions & 0 deletions src/main/java/jdump/output/FileOutput.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
26 changes: 26 additions & 0 deletions src/main/java/jdump/output/Output.java
Original file line number Diff line number Diff line change
@@ -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
}
}
41 changes: 41 additions & 0 deletions src/test/java/jdump/dump/DirectoryOutputTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

0 comments on commit 771cb85

Please sign in to comment.