blob: 75a17f3ce390ea6f05db840a88e04e1645cee251 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dalvik.system.profiler;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* <pre> {@code
* BinaryHprofReader reader = new BinaryHprofReader(new BufferedInputStream(inputStream));
* reader.setStrict(false); // for RI compatability
* reader.read();
* inputStream.close();
* reader.getVersion();
* reader.getHprofData();
* }</pre>
*/
public final class BinaryHprofReader {
private static final boolean TRACE = false;
private final DataInputStream in;
/**
* By default we try to strictly validate rules followed by
* our HprofWriter. For example, every end thread is preceded
* by a matching start thread.
*/
private boolean strict = true;
/**
* version string from header after read has been performed,
* otherwise null. nullness used to detect if callers try to
* access data before read is called.
*/
private String version;
private final Map<HprofData.StackTrace, int[]> stackTraces
= new HashMap<HprofData.StackTrace, int[]>();
private final HprofData hprofData = new HprofData(stackTraces);
private final Map<Integer, String> idToString = new HashMap<Integer, String>();
private final Map<Integer, String> idToClassName = new HashMap<Integer, String>();
private final Map<Integer, StackTraceElement> idToStackFrame
= new HashMap<Integer, StackTraceElement>();
private final Map<Integer, HprofData.StackTrace> idToStackTrace
= new HashMap<Integer, HprofData.StackTrace>();
/**
* Creates a BinaryHprofReader around the specified {@code
* inputStream}
*/
public BinaryHprofReader(InputStream inputStream) throws IOException {
this.in = new DataInputStream(inputStream);
}
public boolean getStrict () {
return strict;
}
public void setStrict (boolean strict) {
if (version != null) {
throw new IllegalStateException("cannot set strict after read()");
}
this.strict = strict;
}
/**
* throws IllegalStateException if read() has not been called.
*/
private void checkRead() {
if (version == null) {
throw new IllegalStateException("data access before read()");
}
}
public String getVersion() {
checkRead();
return version;
}
public HprofData getHprofData() {
checkRead();
return hprofData;
}
/**
* Read the hprof header and records from the input
*/
public void read() throws IOException {
parseHeader();
parseRecords();
}
private void parseHeader() throws IOException {
if (TRACE) {
System.out.println("hprofTag=HEADER");
}
parseVersion();
parseIdSize();
parseTime();
}
private void parseVersion() throws IOException {
String version = BinaryHprof.readMagic(in);
if (version == null) {
throw new MalformedHprofException("Could not find HPROF version");
}
if (TRACE) {
System.out.println("\tversion=" + version);
}
this.version = version;
}
private void parseIdSize() throws IOException {
int idSize = in.readInt();
if (TRACE) {
System.out.println("\tidSize=" + idSize);
}
if (idSize != BinaryHprof.ID_SIZE) {
throw new MalformedHprofException("Unsupported identifier size: " + idSize);
}
}
private void parseTime() throws IOException {
long time = in.readLong();
if (TRACE) {
System.out.println("\ttime=" + Long.toHexString(time) + " " + new Date(time));
}
hprofData.setStartMillis(time);
}
private void parseRecords() throws IOException {
while (parseRecord()) {
;
}
}
/**
* Read and process the next record. Returns true if a
* record was handled, false on EOF.
*/
private boolean parseRecord() throws IOException {
int tagOrEOF = in.read();
if (tagOrEOF == -1) {
return false;
}
byte tag = (byte) tagOrEOF;
int timeDeltaInMicroseconds = in.readInt();
int recordLength = in.readInt();
BinaryHprof.Tag hprofTag = BinaryHprof.Tag.get(tag);
if (TRACE) {
System.out.println("hprofTag=" + hprofTag);
}
if (hprofTag == null) {
skipRecord(hprofTag, recordLength);
return true;
}
String error = hprofTag.checkSize(recordLength);
if (error != null) {
throw new MalformedHprofException(error);
}
switch (hprofTag) {
case CONTROL_SETTINGS:
parseControlSettings();
return true;
case STRING_IN_UTF8:
parseStringInUtf8(recordLength);
return true;
case START_THREAD:
parseStartThread();
return true;
case END_THREAD:
parseEndThread();
return true;
case LOAD_CLASS:
parseLoadClass();
return true;
case STACK_FRAME:
parseStackFrame();
return true;
case STACK_TRACE:
parseStackTrace(recordLength);
return true;
case CPU_SAMPLES:
parseCpuSamples(recordLength);
return true;
case UNLOAD_CLASS:
case ALLOC_SITES:
case HEAP_SUMMARY:
case HEAP_DUMP:
case HEAP_DUMP_SEGMENT:
case HEAP_DUMP_END:
default:
skipRecord(hprofTag, recordLength);
return true;
}
}
private void skipRecord(BinaryHprof.Tag hprofTag, long recordLength) throws IOException {
if (TRACE) {
System.out.println("\tskipping recordLength=" + recordLength);
}
long skipped = in.skip(recordLength);
if (skipped != recordLength) {
throw new EOFException("Expected to skip " + recordLength
+ " bytes but only skipped " + skipped + " bytes");
}
}
private void parseControlSettings() throws IOException {
int flags = in.readInt();
short depth = in.readShort();
if (TRACE) {
System.out.println("\tflags=" + Integer.toHexString(flags));
System.out.println("\tdepth=" + depth);
}
hprofData.setFlags(flags);
hprofData.setDepth(depth);
}
private void parseStringInUtf8(int recordLength) throws IOException {
int stringId = in.readInt();
byte[] bytes = new byte[recordLength - BinaryHprof.ID_SIZE];
readFully(in, bytes);
String string = new String(bytes, "UTF-8");
if (TRACE) {
System.out.println("\tstring=" + string);
}
String old = idToString.put(stringId, string);
if (old != null) {
throw new MalformedHprofException("Duplicate string id: " + stringId);
}
}
private static void readFully(InputStream in, byte[] dst) throws IOException {
int offset = 0;
int byteCount = dst.length;
while (byteCount > 0) {
int bytesRead = in.read(dst, offset, byteCount);
if (bytesRead < 0) {
throw new EOFException();
}
offset += bytesRead;
byteCount -= bytesRead;
}
}
private void parseLoadClass() throws IOException {
int classId = in.readInt();
int classObjectId = readId();
// serial number apparently not a stack trace id. (int vs ID)
// we don't use this field.
int stackTraceSerialNumber = in.readInt();
String className = readString();
if (TRACE) {
System.out.println("\tclassId=" + classId);
System.out.println("\tclassObjectId=" + classObjectId);
System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber);
System.out.println("\tclassName=" + className);
}
String old = idToClassName.put(classId, className);
if (old != null) {
throw new MalformedHprofException("Duplicate class id: " + classId);
}
}
private int readId() throws IOException {
return in.readInt();
}
private String readString() throws IOException {
int id = readId();
if (id == 0) {
return null;
}
String string = idToString.get(id);
if (string == null) {
throw new MalformedHprofException("Unknown string id " + id);
}
return string;
}
private String readClass() throws IOException {
int id = readId();
String string = idToClassName.get(id);
if (string == null) {
throw new MalformedHprofException("Unknown class id " + id);
}
return string;
}
private void parseStartThread() throws IOException {
int threadId = in.readInt();
int objectId = readId();
// stack trace where thread was created.
// serial number apparently not a stack trace id. (int vs ID)
// we don't use this field.
int stackTraceSerialNumber = in.readInt();
String threadName = readString();
String groupName = readString();
String parentGroupName = readString();
if (TRACE) {
System.out.println("\tthreadId=" + threadId);
System.out.println("\tobjectId=" + objectId);
System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber);
System.out.println("\tthreadName=" + threadName);
System.out.println("\tgroupName=" + groupName);
System.out.println("\tparentGroupName=" + parentGroupName);
}
HprofData.ThreadEvent event
= HprofData.ThreadEvent.start(objectId, threadId,
threadName, groupName, parentGroupName);
hprofData.addThreadEvent(event);
}
private void parseEndThread() throws IOException {
int threadId = in.readInt();
if (TRACE) {
System.out.println("\tthreadId=" + threadId);
}
HprofData.ThreadEvent event = HprofData.ThreadEvent.end(threadId);
hprofData.addThreadEvent(event);
}
private void parseStackFrame() throws IOException {
int stackFrameId = readId();
String methodName = readString();
String methodSignature = readString();
String file = readString();
String className = readClass();
int line = in.readInt();
if (TRACE) {
System.out.println("\tstackFrameId=" + stackFrameId);
System.out.println("\tclassName=" + className);
System.out.println("\tmethodName=" + methodName);
System.out.println("\tmethodSignature=" + methodSignature);
System.out.println("\tfile=" + file);
System.out.println("\tline=" + line);
}
StackTraceElement stackFrame = new StackTraceElement(className, methodName, file, line);
StackTraceElement old = idToStackFrame.put(stackFrameId, stackFrame);
if (old != null) {
throw new MalformedHprofException("Duplicate stack frame id: " + stackFrameId);
}
}
private void parseStackTrace(int recordLength) throws IOException {
int stackTraceId = in.readInt();
int threadId = in.readInt();
int frames = in.readInt();
if (TRACE) {
System.out.println("\tstackTraceId=" + stackTraceId);
System.out.println("\tthreadId=" + threadId);
System.out.println("\tframes=" + frames);
}
int expectedLength = 4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE);
if (recordLength != expectedLength) {
throw new MalformedHprofException("Expected stack trace record of size "
+ expectedLength
+ " based on number of frames but header "
+ "specified a length of " + recordLength);
}
StackTraceElement[] stackFrames = new StackTraceElement[frames];
for (int i = 0; i < frames; i++) {
int stackFrameId = readId();
StackTraceElement stackFrame = idToStackFrame.get(stackFrameId);
if (TRACE) {
System.out.println("\tstackFrameId=" + stackFrameId);
System.out.println("\tstackFrame=" + stackFrame);
}
if (stackFrame == null) {
throw new MalformedHprofException("Unknown stack frame id " + stackFrameId);
}
stackFrames[i] = stackFrame;
}
HprofData.StackTrace stackTrace
= new HprofData.StackTrace(stackTraceId, threadId, stackFrames);
if (strict) {
hprofData.addStackTrace(stackTrace, new int[1]);
} else {
// The RI can have duplicate stacks, presumably they
// have a minor race if two samples with the same
// stack are taken around the same time. if we have a
// duplicate, just skip adding it to hprofData, but
// register it locally in idToStackFrame. if it seen
// in CPU_SAMPLES, we will find a StackTrace is equal
// to the first, so they will share a countCell.
int[] countCell = stackTraces.get(stackTrace);
if (countCell == null) {
hprofData.addStackTrace(stackTrace, new int[1]);
}
}
HprofData.StackTrace old = idToStackTrace.put(stackTraceId, stackTrace);
if (old != null) {
throw new MalformedHprofException("Duplicate stack trace id: " + stackTraceId);
}
}
private void parseCpuSamples(int recordLength) throws IOException {
int totalSamples = in.readInt();
int samplesCount = in.readInt();
if (TRACE) {
System.out.println("\ttotalSamples=" + totalSamples);
System.out.println("\tsamplesCount=" + samplesCount);
}
int expectedLength = 4 + 4 + (samplesCount * (4 + 4));
if (recordLength != expectedLength) {
throw new MalformedHprofException("Expected CPU samples record of size "
+ expectedLength
+ " based on number of samples but header "
+ "specified a length of " + recordLength);
}
int total = 0;
for (int i = 0; i < samplesCount; i++) {
int count = in.readInt();
int stackTraceId = in.readInt();
if (TRACE) {
System.out.println("\tcount=" + count);
System.out.println("\tstackTraceId=" + stackTraceId);
}
HprofData.StackTrace stackTrace = idToStackTrace.get(stackTraceId);
if (stackTrace == null) {
throw new MalformedHprofException("Unknown stack trace id " + stackTraceId);
}
if (count == 0) {
throw new MalformedHprofException("Zero sample count for stack trace "
+ stackTrace);
}
int[] countCell = stackTraces.get(stackTrace);
if (strict) {
if (countCell[0] != 0) {
throw new MalformedHprofException("Setting sample count of stack trace "
+ stackTrace + " to " + count
+ " found it was already initialized to "
+ countCell[0]);
}
} else {
// Coalesce counts from duplicate stack traces.
// For more on this, see comments in parseStackTrace.
count += countCell[0];
}
countCell[0] = count;
total += count;
}
if (strict && totalSamples != total) {
throw new MalformedHprofException("Expected a total of " + totalSamples
+ " samples but saw " + total);
}
}
}