8257234: Add gz option to SA jmap to write a gzipped heap dump

Reviewed-by: cjplummer, ysuenaga, sspitsyn
This commit is contained in:
Lin Zang 2021-02-25 12:09:55 +00:00 committed by Jie Fu
parent aa35b42354
commit c54724da14
7 changed files with 634 additions and 105 deletions

View File

@ -163,7 +163,6 @@ public class CommandProcessor {
Tokens(String cmd) {
input = cmd;
// check for quoting
int quote = cmd.indexOf('"');
ArrayList<String> t = new ArrayList<>();
@ -1787,25 +1786,60 @@ public class CommandProcessor {
sysProps.run();
}
},
new Command("dumpheap", "dumpheap [filename]", false) {
new Command("dumpheap", "dumpheap [gz=<1-9>] [filename]", false) {
public void doit(Tokens t) {
if (t.countTokens() > 1) {
int cntTokens = t.countTokens();
if (cntTokens > 2) {
err.println("More than 2 options specified: " + cntTokens);
usage();
} else {
JMap jmap = new JMap();
String filename;
if (t.countTokens() == 1) {
filename = t.nextToken();
return;
}
JMap jmap = new JMap();
String filename = "heap.bin";
int gzlevel = 0;
/*
* Possible command:
* dumpheap gz=1 file;
* dumpheap gz=1;
* dumpheap file;
* dumpheap
*
* Use default filename if cntTokens == 0.
* Handle cases with cntTokens == 1 or 2.
*/
if (cntTokens == 1) { // first argument could be filename or "gz="
String option = t.nextToken();
if (!option.startsWith("gz=")) {
filename = option;
} else {
filename = "heap.bin";;
}
try {
jmap.writeHeapHprofBin(filename);
} catch (Exception e) {
err.println("Error: " + e);
if (verboseExceptions) {
e.printStackTrace(err);
gzlevel = parseHeapDumpCompressionLevel(option);
if (gzlevel == 0) {
usage();
return;
}
filename = "heap.bin.gz";
}
}
if (cntTokens == 2) { // first argument is "gz=" followed by filename
String option = t.nextToken();
gzlevel = parseHeapDumpCompressionLevel(option);
if (gzlevel == 0) {
usage();
return;
}
filename = t.nextToken();
if (filename.startsWith("gz=")) {
err.println("Filename should not start with \"gz=\": " + filename);
usage();
return;
}
}
try {
jmap.writeHeapHprofBin(filename, gzlevel);
} catch (Exception e) {
err.println("Error: " + e);
if (verboseExceptions) {
e.printStackTrace(err);
}
}
}
@ -2080,4 +2114,34 @@ public class CommandProcessor {
}
}
}
/* Parse compression level
* @return 1-9 compression level
* 0 compression level is illegal
*/
private int parseHeapDumpCompressionLevel(String option) {
String[] keyValue = option.split("=");
if (!keyValue[0].equals("gz")) {
err.println("Expected option is \"gz=\"");
return 0;
}
if (keyValue.length != 2) {
err.println("Exactly one argument is expected for option \"gz\"");
return 0;
}
int gzl = 0;
String level = keyValue[1];
try {
gzl = Integer.parseInt(level);
} catch (NumberFormatException e) {
err.println("gz option value not an integer ("+level+")");
return 0;
}
if (gzl < 1 || gzl > 9) {
err.println("Compression level out of range (1-9): " + level);
return 0;
}
return gzl;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2021, 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
@ -126,7 +126,8 @@ public class SALauncher {
System.out.println(" <no option> To print same info as Solaris pmap.");
System.out.println(" --heap To print java heap summary.");
System.out.println(" --binaryheap To dump java heap in hprof binary format.");
System.out.println(" --dumpfile <name> The name of the dump file.");
System.out.println(" --dumpfile <name> The name of the dump file. Only valid with --binaryheap.");
System.out.println(" --gz <1-9> The compression level for gzipped dump file. Only valid with --binaryheap.");
System.out.println(" --histo To print histogram of java object heap.");
System.out.println(" --clstats To print class loader statistics.");
System.out.println(" --finalizerinfo To print information on objects awaiting finalization.");
@ -301,33 +302,40 @@ public class SALauncher {
}
private static void runJMAP(String[] oldArgs) {
Map<String, String> longOptsMap = Map.of("exe=", "exe",
"core=", "core",
"pid=", "pid",
"connect=", "connect",
"heap", "-heap",
"binaryheap", "binaryheap",
"dumpfile=", "dumpfile",
"histo", "-histo",
"clstats", "-clstats",
"finalizerinfo", "-finalizerinfo");
Map<String, String> longOptsMap = Map.ofEntries(
Map.entry("exe=", "exe"),
Map.entry("core=", "core"),
Map.entry("pid=", "pid"),
Map.entry("connect=", "connect"),
Map.entry("heap", "-heap"),
Map.entry("binaryheap", "binaryheap"),
Map.entry("dumpfile=", "dumpfile"),
Map.entry("gz=", "gz"),
Map.entry("histo", "-histo"),
Map.entry("clstats", "-clstats"),
Map.entry("finalizerinfo", "-finalizerinfo"));
Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
boolean requestHeapdump = newArgMap.containsKey("binaryheap");
String dumpfile = newArgMap.get("dumpfile");
String gzLevel = newArgMap.get("gz");
String command = "-heap:format=b";
if (!requestHeapdump && (dumpfile != null)) {
throw new IllegalArgumentException("Unexpected argument: dumpfile");
}
if (requestHeapdump) {
if (dumpfile == null) {
newArgMap.put("-heap:format=b", null);
} else {
newArgMap.put("-heap:format=b,file=" + dumpfile, null);
if (gzLevel != null) {
command += ",gz=" + gzLevel;
}
if (dumpfile != null) {
command += ",file=" + dumpfile;
}
newArgMap.put(command, null);
}
newArgMap.remove("binaryheap");
newArgMap.remove("dumpfile");
newArgMap.remove("gz");
JMap.main(buildAttachArgs(newArgMap, false));
}
@ -485,6 +493,8 @@ public class SALauncher {
} catch (SAGetoptException e) {
System.err.println(e.getMessage());
toolHelp(args[0]);
// Exit with error status
System.exit(1);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2021, 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
@ -50,16 +50,21 @@ public class JMap extends Tool {
}
protected String getCommandFlags() {
return "-heap|-heap:format=b|-histo|-clstats|-finalizerinfo";
return "-heap|-heap:format=b[,gz=<1-9>][,file=<dumpfile>]|-heap:format=x[,file=<dumpfile>]|{-histo|-clstats|-finalizerinfo";
}
protected void printFlagsUsage() {
System.out.println(" <no option>\tto print same info as Solaris pmap");
System.out.println(" -heap\tto print java heap summary");
System.out.println(" -heap:format=b\tto dump java heap in hprof binary format");
System.out.println(" -histo\tto print histogram of java object heap");
System.out.println(" -clstats\tto print class loader statistics");
System.out.println(" -finalizerinfo\tto print information on objects awaiting finalization");
System.out.println(" <no option>\tTo print same info as Solaris pmap.");
System.out.println(" -heap\tTo print java heap summary.");
System.out.println(" -heap:format=b[,gz=<1-9>][,file=<dumpfile>] \tTo dump java heap in hprof binary format.");
System.out.println(" \tIf gz specified, the heap dump is written in gzipped format");
System.out.println(" \tusing the given compression level.");
System.err.println(" \t1 (recommended) is the fastest, 9 the strongest compression.");
System.out.println(" -heap:format=x[,file=<dumpfile>] \tTo dump java heap in GXL format.");
System.out.println(" \tPlease be aware that \"gz\" option is not valid for heap dump in GXL format.");
System.out.println(" -histo\tTo print histogram of java object heap.");
System.out.println(" -clstats\tTo print class loader statistics.");
System.out.println(" -finalizerinfo\tTo print information on objects awaiting finalization.");
super.printFlagsUsage();
}
@ -72,6 +77,7 @@ public class JMap extends Tool {
public static final int MODE_FINALIZERINFO = 6;
private static String dumpfile = "heap.bin";
private static int gzLevel = 0;
public void run() {
Tool tool = null;
@ -94,7 +100,7 @@ public class JMap extends Tool {
break;
case MODE_HEAP_GRAPH_HPROF_BIN:
writeHeapHprofBin(dumpfile);
writeHeapHprofBin(dumpfile, gzLevel);
return;
case MODE_HEAP_GRAPH_GXL:
@ -151,6 +157,26 @@ public class JMap extends Tool {
System.exit(1);
}
dumpfile = keyValue[1];
} else if (keyValue[0].equals("gz")) {
if (mode == MODE_HEAP_GRAPH_GXL) {
System.err.println("\"gz\" option is not compatible with heap dump in GXL format");
System.exit(1);
}
if (keyValue.length == 1) {
System.err.println("Argument is expected for \"gz\"");
System.exit(1);
}
String level = keyValue[1];
try {
gzLevel = Integer.parseInt(level);
} catch (NumberFormatException e) {
System.err.println("\"gz\" option value not an integer ("+level+")");
System.exit(1);
}
if (gzLevel < 1 || gzLevel > 9) {
System.err.println("compression level out of range (1-9): " + level);
System.exit(1);
}
} else {
System.err.println("unknown option:" + keyValue[0]);
@ -176,9 +202,17 @@ public class JMap extends Tool {
jmap.execute(args);
}
public boolean writeHeapHprofBin(String fileName) {
public boolean writeHeapHprofBin(String fileName, int gzLevel) {
try {
HeapGraphWriter hgw = new HeapHprofBinWriter();
HeapGraphWriter hgw;
if (gzLevel == 0) {
hgw = new HeapHprofBinWriter();
} else if (gzLevel >=1 && gzLevel <= 9) {
hgw = new HeapHprofBinWriter(gzLevel);
} else {
System.err.println("Illegal compression level: " + gzLevel);
return false;
}
hgw.write(fileName);
System.out.println("heap written to " + fileName);
return true;
@ -188,7 +222,7 @@ public class JMap extends Tool {
}
public boolean writeHeapHprofBin() {
return writeHeapHprofBin("heap.bin");
return writeHeapHprofBin("heap.bin", -1);
}
private boolean writeHeapGXL(String fileName) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2021, 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
@ -25,8 +25,11 @@
package sun.jvm.hotspot.utilities;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.*;
import java.util.*;
import java.util.zip.*;
import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.memory.*;
import sun.jvm.hotspot.oops.*;
@ -386,15 +389,39 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
public HeapHprofBinWriter() {
this.KlassMap = new ArrayList<Klass>();
this.names = new HashSet<Symbol>();
this.gzLevel = 0;
}
public HeapHprofBinWriter(int gzLevel) {
this.KlassMap = new ArrayList<Klass>();
this.names = new HashSet<Symbol>();
this.gzLevel = gzLevel;
}
public synchronized void write(String fileName) throws IOException {
VM vm = VM.getVM();
// Check whether we should dump the heap as segments
useSegmentedHeapDump = isCompression() ||
(vm.getUniverse().heap().used() > HPROF_SEGMENTED_HEAP_DUMP_THRESHOLD);
// open file stream and create buffered data output stream
fos = new FileOutputStream(fileName);
out = new DataOutputStream(new BufferedOutputStream(fos));
hprofBufferedOut = null;
OutputStream dataOut = fos;
if (useSegmentedHeapDump) {
if (isCompression()) {
dataOut = new GZIPOutputStream(fos) {
{
this.def.setLevel(gzLevel);
}
};
}
hprofBufferedOut = new SegmentedOutputStream(dataOut);
} else {
hprofBufferedOut = new SegmentedOutputStream(fos, false /* allowSegmented */);
}
out = new DataOutputStream(hprofBufferedOut);
dbg = vm.getDebugger();
objectHeap = vm.getObjectHeap();
@ -419,9 +446,6 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
FLOAT_SIZE = objectHeap.getFloatSize();
DOUBLE_SIZE = objectHeap.getDoubleSize();
// Check weather we should dump the heap as segments
useSegmentedHeapDump = vm.getUniverse().heap().used() > HPROF_SEGMENTED_HEAP_DUMP_THRESHOLD;
// hprof bin format header
writeFileHeader();
@ -447,10 +471,11 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
// flush buffer stream.
out.flush();
// Fill in final length
fillInHeapRecordLength();
if (useSegmentedHeapDump) {
if (!useSegmentedHeapDump) {
// Fill in final length.
fillInHeapRecordLength();
} else {
hprofBufferedOut.finish();
// Write heap segment-end record
out.writeByte((byte) HPROF_HEAP_DUMP_END);
out.writeInt(0);
@ -459,19 +484,18 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
// flush buffer stream and throw it.
out.flush();
out.close();
out = null;
// close the file stream
fos.close();
hprofBufferedOut = null;
}
@Override
protected void writeHeapRecordPrologue() throws IOException {
if (currentSegmentStart == 0) {
// write heap data header, depending on heap size use segmented heap
// format
out.writeByte((byte) (useSegmentedHeapDump ? HPROF_HEAP_DUMP_SEGMENT
: HPROF_HEAP_DUMP));
if (useSegmentedHeapDump) {
hprofBufferedOut.enterSegmentMode();
} else if (currentSegmentStart == 0) {
// write heap data header
out.writeByte((byte) (HPROF_HEAP_DUMP));
out.writeInt(0);
// remember position of dump length, we will fixup
@ -486,15 +510,12 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
@Override
protected void writeHeapRecordEpilogue() throws IOException {
if (useSegmentedHeapDump) {
out.flush();
if ((fos.getChannel().position() - currentSegmentStart - 4L) >= HPROF_SEGMENTED_HEAP_DUMP_SEGMENT_SIZE) {
fillInHeapRecordLength();
currentSegmentStart = 0;
}
hprofBufferedOut.exitSegmentMode();
}
}
private void fillInHeapRecordLength() throws IOException {
assert !useSegmentedHeapDump : "fillInHeapRecordLength is not supported for segmented heap dump";
// now get the current position to calculate length
long dumpEnd = fos.getChannel().position();
@ -513,13 +534,10 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
// seek the position to write length
fos.getChannel().position(currentSegmentStart);
// write length
int dumpLen = (int) dumpLenLong;
// write length as integer
fos.write((dumpLen >>> 24) & 0xFF);
fos.write((dumpLen >>> 16) & 0xFF);
fos.write((dumpLen >>> 8) & 0xFF);
fos.write((dumpLen >>> 0) & 0xFF);
byte[] lenBytes = genByteArrayFromInt(dumpLen);
fos.write(lenBytes);
//Reset to previous current position
fos.getChannel().position(currentPosition);
@ -569,8 +587,10 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
long originalLengthInBytes = originalArrayLength * typeSize;
// calculate the length of heap data
// only process when segmented heap dump is not used, since SegmentedOutputStream
// could create segment automatically.
long currentRecordLength = (dumpEnd - currentSegmentStart - 4L);
if (currentRecordLength > 0 &&
if ((!useSegmentedHeapDump) && currentRecordLength > 0 &&
(currentRecordLength + headerSize + originalLengthInBytes) > MAX_U4_VALUE) {
fillInHeapRecordLength();
currentSegmentStart = 0;
@ -1226,6 +1246,18 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
return size;
}
private boolean isCompression() {
return (gzLevel >= 1 && gzLevel <= 9);
}
// Convert integer to byte array with BIG_ENDIAN byte order.
private static byte[] genByteArrayFromInt(int value) {
ByteBuffer intBuffer = ByteBuffer.allocate(4);
intBuffer.order(ByteOrder.BIG_ENDIAN);
intBuffer.putInt(value);
return intBuffer.array();
}
// We don't have allocation site info. We write a dummy
// stack trace with this id.
private static final int DUMMY_STACK_TRACE_ID = 1;
@ -1233,9 +1265,11 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
private DataOutputStream out;
private FileOutputStream fos;
private SegmentedOutputStream hprofBufferedOut;
private Debugger dbg;
private ObjectHeap objectHeap;
private ArrayList<Klass> KlassMap;
private int gzLevel;
// oopSize of the debuggee
private int OBJ_ID_SIZE;
@ -1274,4 +1308,221 @@ public class HeapHprofBinWriter extends AbstractHeapGraphWriter {
}
private Map<InstanceKlass, ClassData> classDataCache = new HashMap<>();
/**
* The class implements a buffered output stream for segmented data dump.
* It is used inside HeapHprofBinWritter only for heap dump.
* Because the current implementation of segmented heap dump needs to update
* the segment size at segment header, and because it is hard to modify the
* compressed data after they are written to file, this class first saves the
* uncompressed data into an internal buffer, and then writes through to the
* GZIPOutputStream when the whole segmented data are ready and the size is updated.
* If the data to be written are larger than internal buffer, or the internal buffer
* is full, the internal buffer will be extend to a larger one.
* This class defines a switch to turn on/off the segmented mode. If turned off,
* it behaves the same as BufferedOutputStream.
* */
private class SegmentedOutputStream extends BufferedOutputStream {
/**
* Creates a new buffered output stream to support segmented heap dump data.
*
* @param out the underlying output stream.
* @param allowSegmented whether allow segmental dump.
*/
public SegmentedOutputStream(OutputStream out, boolean allowSegmented) {
super(out, 8192);
segmentMode = false;
this.allowSegmented = allowSegmented;
segmentBuffer = new byte[SEGMENT_BUFFER_SIZE];
segmentWritten = 0;
}
/**
* Creates a new buffered output stream to support segmented heap dump data.
*
* @param out the underlying output stream.
*/
public SegmentedOutputStream(OutputStream out) {
this(out, true);
}
/**
* Writes the specified byte to this buffered output stream.
*
* @param b the byte to be written.
* @throws IOException if an I/O error occurs.
*/
@Override
public synchronized void write(int b) throws IOException {
if (segmentMode) {
if (segmentWritten == 0) {
// At the begining of the segment.
writeSegmentHeader();
} else if (segmentWritten == segmentBuffer.length) {
// Internal buffer is full, extend a larger one.
int newSize = segmentBuffer.length + SEGMENT_BUFFER_INC_SIZE;
byte newBuf[] = new byte[newSize];
System.arraycopy(segmentBuffer, 0, newBuf, 0, segmentWritten);
segmentBuffer = newBuf;
}
segmentBuffer[segmentWritten++] = (byte)b;
return;
}
super.write(b);
}
/**
* Writes {@code len} bytes from the specified byte array
* starting at offset {@code off} to this output stream.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @throws IOException if an I/O error occurs.
*/
@Override
public synchronized void write(byte b[], int off, int len) throws IOException {
if (segmentMode) {
if (segmentWritten == 0) {
writeSegmentHeader();
}
// Data size is larger than segment buffer length, extend segment buffer.
if (segmentWritten + len > segmentBuffer.length) {
int newSize = segmentBuffer.length + Math.max(SEGMENT_BUFFER_INC_SIZE, len);
byte newBuf[] = new byte[newSize];
System.arraycopy(segmentBuffer, 0, newBuf, 0, segmentWritten);
segmentBuffer = newBuf;
}
System.arraycopy(b, off, segmentBuffer, segmentWritten, len);
segmentWritten += len;
return;
}
super.write(b, off, len);
}
/**
* Flushes this buffered output stream. This forces any buffered
* output bytes to be written out to the underlying output stream.
*
* @throws IOException if an I/O error occurs.
* @see java.io.FilterOutputStream#out
*/
@Override
public synchronized void flush() throws IOException {
if (segmentMode) {
// The case that nothing has been written in segment.
if (segmentWritten == 0) return;
// There must be more data than just header size written for non-empty segment.
assert segmentWritten > SEGMENT_HEADER_SIZE
: "invalid header in segmented mode";
if (segmentWritten > (segmentBuffer.length)) {
throw new RuntimeException("Heap segment size overflow.");
}
if (segmentWritten > SEGMENT_HEADER_SIZE) {
fillSegmentSize(segmentWritten - SEGMENT_HEADER_SIZE);
super.write(segmentBuffer, 0, segmentWritten);
super.flush();
segmentWritten = 0;
}
return;
}
super.flush();
}
/**
* Enters segmented mode, flush buffered data and set flag.
*/
public void enterSegmentMode() throws IOException {
if (allowSegmented && !segmentMode && segmentWritten == 0) {
super.flush();
segmentMode = true;
segmentWritten = 0;
}
}
/**
* Before finish, flush all data in buffer.
*/
public void finish() throws IOException {
if (allowSegmented && segmentMode) {
flush();
assert segmentWritten == 0;
segmentMode = false;
}
}
/**
* Exits segmented mode, flush segmented data.
* @param force flush data regardless whether the buffer is full
*/
public void exitSegmentMode() throws IOException {
if (allowSegmented && segmentMode && shouldFlush()) {
flush();
assert segmentWritten == 0;
segmentMode = false;
}
}
/**
* Check whether the data should be flush based on data saved in
* segmentBuffer.
* This method is used to control the segments number and the memory usage.
* If segment is too small, there will be lots segments in final dump file.
* If it is too large, lots of memory is used in RAM.
*/
private boolean shouldFlush() {
// return true if data in segmentBuffer has been extended.
return segmentWritten > SEGMENT_BUFFER_SIZE;
}
/**
* Writes the write segment header into internal buffer.
*/
private void writeSegmentHeader() {
assert segmentWritten == 0;
segmentBuffer[segmentWritten++] = (byte)HPROF_HEAP_DUMP_SEGMENT;
writeInteger(0);
// segment size, write dummy length of 0 and we'll fix it later.
writeInteger(0);
}
/**
* Fills the segmented data size into the header.
*/
private void fillSegmentSize(int size) {
byte[] lenBytes = genByteArrayFromInt(size);
System.arraycopy(lenBytes, 0, segmentBuffer, 5, 4);
}
/**
* Writes an {@code int} to the internal segment buffer
* {@code written} is incremented by {@code 4}.
*/
private final void writeInteger(int value) {
byte[] intBytes = genByteArrayFromInt(value);
System.arraycopy(intBytes, 0, segmentBuffer, segmentWritten, 4);
segmentWritten += 4;
}
// The buffer size for segmentBuffer.
// Since it is hard to calculate and fill the data size of an segment in compressed
// data, making the segmented data stored in this buffer could help rewrite the data
// size before the segmented data are written to underlying GZIPOutputStream.
private static final int SEGMENT_BUFFER_SIZE = 1 << 20;
// Buffer size used to extend the segment buffer.
private static final int SEGMENT_BUFFER_INC_SIZE = 1 << 10;
// Headers:
// 1 byte for HPROF_HEAP_DUMP_SEGMENT
// 4 bytes for timestamp
// 4 bytes for size
private static final int SEGMENT_HEADER_SIZE = 9;
// Segment support.
private boolean segmentMode;
private boolean allowSegmented;
private byte segmentBuffer[];
private int segmentWritten;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2021, 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
@ -24,7 +24,13 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import static jdk.test.lib.Asserts.assertTrue;
import static jdk.test.lib.Asserts.assertFalse;
import jdk.test.lib.hprof.HprofParser;
import jdk.test.lib.apps.LingeredApp;
import jdk.test.lib.hprof.parser.HprofReader;
import jtreg.SkippedException;
@ -39,6 +45,10 @@ import jtreg.SkippedException;
*/
public class ClhsdbDumpheap {
// The default heap dump file name defined in JDK.
private static final String HEAP_DUMP_FILENAME_DEFAULT = "heap.bin";
private static final String HEAP_DUMP_GZIPED_FILENAME_DEFAULT = "heap.bin.gz";
public static void printStackTraces(String file) {
try {
System.out.println("HprofReader.getStack() output:");
@ -51,26 +61,116 @@ public class ClhsdbDumpheap {
}
}
private static void verifyDumpFile(File dump) throws Exception {
assertTrue(dump.exists() && dump.isFile(), "Could not create dump file " + dump.getAbsolutePath());
printStackTraces(dump.getAbsolutePath());
}
private static class SubTest {
private String cmd;
private String fileName;
private String expectedOutput;
boolean compression;
boolean needVerify;
public SubTest(String comm, String fName, boolean isComp, boolean verify, String expected) {
cmd = comm;
fileName = fName;
expectedOutput = expected;
compression = isComp;
needVerify = verify;
}
public String getCmd() { return cmd; }
public String getFileName() { return fileName; }
public String getExpectedOutput() { return expectedOutput; }
public boolean isCompression() { return compression; }
public boolean needVerify() { return needVerify; }
}
private static void runTest(long appPid, SubTest subtest) throws Exception {
ClhsdbLauncher test = new ClhsdbLauncher();
String fileName = subtest.getFileName();
String cmd = subtest.getCmd();
String expectedOutput = subtest.getExpectedOutput();
boolean compression = subtest.isCompression();
/* The expected generated file, used to distinguish with fileName in case fileName is blank or null */
String expectedFileName = fileName;
if (fileName == null || fileName.length() == 0) {
if (!compression) {
expectedFileName = HEAP_DUMP_FILENAME_DEFAULT;
} else {
expectedFileName = HEAP_DUMP_GZIPED_FILENAME_DEFAULT;
}
}
assertTrue (expectedFileName != null && expectedFileName.length() > 0,
"Expected generated file name must have value");
File file = new File(expectedFileName);
if (file.exists()) {
file.delete();
}
String command = cmd + fileName;
List<String> cmds = List.of(command);
Map<String, List<String>> expStrMap = new HashMap<>();
expStrMap.put(command, List.of(expectedOutput));
test.run(appPid, cmds, expStrMap, null);
if (subtest.needVerify()) {
verifyDumpFile(file);
}
file.delete();
}
public static void main(String[] args) throws Exception {
System.out.println("Starting ClhsdbDumpheap test");
LingeredApp theApp = null;
try {
// Use file name different with JDK's default value "heap.bin".
String heapDumpFileName = "heapdump.bin";
ClhsdbLauncher test = new ClhsdbLauncher();
String heapDumpFileNameGz = "heapdump.bin.gz";
theApp = new LingeredApp();
LingeredApp.startApp(theApp);
System.out.println("Started LingeredApp with pid " + theApp.getPid());
List<String> cmds = List.of("dumpheap " + heapDumpFileName);
Map<String, List<String>> expStrMap = new HashMap<>();
expStrMap.put("dumpheap", List.of(
"heap written to " + heapDumpFileName));
test.run(theApp.getPid(), cmds, expStrMap, null);
printStackTraces(heapDumpFileName);
SubTest[] subtests = new SubTest[] {
new SubTest("dumpheap ", heapDumpFileName, false/*compression*/, true,/*verify*/
"heap written to " + heapDumpFileName),
new SubTest("dumpheap gz=1 ", heapDumpFileNameGz, true, true,
"heap written to " + heapDumpFileNameGz),
new SubTest("dumpheap gz=9 ", heapDumpFileNameGz, true, true,
"heap written to " + heapDumpFileNameGz),
new SubTest("dumpheap gz=0 ", heapDumpFileNameGz, true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]"),
new SubTest("dumpheap gz=100 ", heapDumpFileNameGz, true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]"),
new SubTest("dumpheap gz= ", heapDumpFileNameGz, true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]"),
new SubTest("dumpheap gz ", heapDumpFileNameGz, true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]"),
new SubTest("dumpheap", "", false, true,
"heap written to " + HEAP_DUMP_FILENAME_DEFAULT),
new SubTest("dumpheap gz=1", "", true, true,
"heap written to " + HEAP_DUMP_GZIPED_FILENAME_DEFAULT),
new SubTest("dumpheap gz=9", "", true, true,
"heap written to " + HEAP_DUMP_GZIPED_FILENAME_DEFAULT),
new SubTest("dumpheap gz=0", "", true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]"),
new SubTest("dumpheap gz=100", "", true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]"),
// Command "dumpheap gz=".
new SubTest("dumpheap ", "gz=", true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]"),
// Command "dumpheap gz".
new SubTest("dumpheap ", "gz", false, true, "heap written to gz"),
// Command "dump heap gz=1 gz=2".
new SubTest("dumpheap gz=1", "gz=2", true, false,
"Usage: dumpheap \\[gz=<1-9>\\] \\[filename\\]")
};
// Run subtests
for (int i = 0; i < subtests.length;i++) {
runTest(theApp.getPid(), subtests[i]);
}
} catch (SkippedException se) {
throw se;
} catch (Exception ex) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2021, 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
@ -49,13 +49,13 @@ import jdk.test.lib.SA.SATestUtils;
public class HeapDumpTest {
private static LingeredAppWithExtendedChars theApp = null;
private static final String SUCCESS_STRING = "heap written to";
/**
*
* @param vmArgs - tool arguments to launch jhsdb
* @return exit code of tool
*/
public static void launch(String expectedMessage, List<String> toolArgs)
public static void launch(int expectedExitValue, List<String> toolArgs)
throws IOException {
System.out.println("Starting LingeredApp");
@ -81,9 +81,12 @@ public class HeapDumpTest {
System.out.println(output.getStdout());
System.out.println("stderr:");
System.out.println(output.getStderr());
output.shouldContain(expectedMessage);
output.shouldHaveExitValue(0);
output.shouldHaveExitValue(expectedExitValue);
if (expectedExitValue == 0) {
output.shouldContain(SUCCESS_STRING);
} else {
output.stdoutShouldNotContain(SUCCESS_STRING);
}
} catch (Exception ex) {
throw new RuntimeException("Test ERROR " + ex, ex);
} finally {
@ -91,10 +94,9 @@ public class HeapDumpTest {
}
}
public static void launch(String expectedMessage, String... toolArgs)
public static void launch(int expectedExitValue, String... toolArgs)
throws IOException {
launch(expectedMessage, Arrays.asList(toolArgs));
launch(expectedExitValue, Arrays.asList(toolArgs));
}
public static void printStackTraces(String file) throws IOException {
@ -108,31 +110,66 @@ public class HeapDumpTest {
}
}
public static void testHeapDump() throws IOException {
public static void testHeapDump(SubTest subtest) throws IOException {
String gzOption = subtest.getGzOption();
boolean checkSuccess = subtest.needCheckSuccess();
int expectedExitValue = checkSuccess ? 0 : 1;
File dump = new File("jhsdb.jmap.heap." +
System.currentTimeMillis() + ".hprof");
if (dump.exists()) {
dump.delete();
}
if (gzOption == null || gzOption.length() == 0) {
launch(expectedExitValue, "jmap",
"--binaryheap", "--dumpfile=" + dump.getAbsolutePath());
} else {
launch(expectedExitValue, "jmap",
"--binaryheap", gzOption, "--dumpfile=" + dump.getAbsolutePath());
}
launch("heap written to", "jmap",
"--binaryheap", "--dumpfile=" + dump.getAbsolutePath());
if (checkSuccess) {
assertTrue(dump.exists() && dump.isFile(),
"Could not create dump file " + dump.getAbsolutePath());
assertTrue(dump.exists() && dump.isFile(),
"Could not create dump file " + dump.getAbsolutePath());
printStackTraces(dump.getAbsolutePath());
dump.delete();
printStackTraces(dump.getAbsolutePath());
dump.delete();
} else {
assertTrue(!dump.exists(), "Unexpected file created: " + dump.getAbsolutePath());
}
}
public static void main(String[] args) throws Exception {
SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work.
testHeapDump();
SubTest[] subtests = new SubTest[] {
new SubTest("", true/*checkSuccess*/),
new SubTest("--gz=1", true),
new SubTest("--gz=9", true),
new SubTest("--gz=0", false),
new SubTest("--gz=100", false),
new SubTest("--gz=", false),
new SubTest("--gz", false),
};
// Run subtests
for (int i = 0; i < subtests.length;i++) {
testHeapDump(subtests[i]);
}
// The test throws RuntimeException on error.
// IOException is thrown if LingeredApp can't start because of some bad
// environment condition
System.out.println("Test PASSED");
}
private static class SubTest {
private String gzOption;
boolean needCheckSuccess;
public SubTest(String gzOpt, boolean checkSuccess) {
gzOption = gzOpt;
needCheckSuccess = checkSuccess;
}
public String getGzOption() { return gzOption; }
public boolean needCheckSuccess() { return needCheckSuccess; }
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2021, 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
@ -33,6 +33,7 @@
package jdk.test.lib.hprof.parser;
import java.io.*;
import java.util.zip.GZIPInputStream;
import jdk.test.lib.hprof.model.*;
/**
@ -45,6 +46,8 @@ import jdk.test.lib.hprof.model.*;
public abstract class Reader {
protected PositionDataInputStream in;
// Magic number of gzip dump file header.
private static final int GZIP_HEADER_MAGIC = 0x1f8b08;
protected Reader(PositionDataInputStream in) {
this.in = in;
@ -142,9 +145,39 @@ public abstract class Reader {
true, debugLevel);
r.read();
return r.printStackTraces();
} else if ((i >>> 8) == GZIP_HEADER_MAGIC) {
// Possible gziped file, try decompress it and get the stack trace.
in.close();
String deCompressedFile = "heapdump" + System.currentTimeMillis() + ".hprof";
File out = new File(deCompressedFile);
// Decompress to get dump file.
try {
GZIPInputStream gis = new GZIPInputStream(new FileInputStream(heapFile));
FileOutputStream fos = new FileOutputStream(out);
byte[] buffer = new byte[1024 * 1024];
int len = 0;
while ((len = gis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
// Check dump data header and print stack trace.
PositionDataInputStream in2 = new PositionDataInputStream(
new BufferedInputStream(new FileInputStream(out)));
i = in2.readInt();
if (i == HprofReader.MAGIC_NUMBER) {
HprofReader r
= new HprofReader(deCompressedFile, in2, dumpNumber,
true, debugLevel);
r.read();
return r.printStackTraces();
}
} catch (Exception e) {
throw new IOException("Can not decompress the compressed hprof file", e);
}
out.delete();
} else {
throw new IOException("Unrecognized magic number: " + i);
}
}
return null;
}
}