| /* |
| * 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); |
| } |
| } |
| } |