Home | History | Annotate | Download | only in profiler
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package dalvik.system.profiler;
     18 
     19 import java.io.BufferedInputStream;
     20 import java.io.DataInputStream;
     21 import java.io.EOFException;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.util.Date;
     25 import java.util.HashMap;
     26 import java.util.Map;
     27 
     28 /**
     29  * <pre>   {@code
     30  * BinaryHprofReader reader = new BinaryHprofReader(new BufferedInputStream(inputStream));
     31  * reader.setStrict(false); // for RI compatability
     32  * reader.read();
     33  * inputStream.close();
     34  * reader.getVersion();
     35  * reader.getHprofData();
     36  * }</pre>
     37  */
     38 public final class BinaryHprofReader {
     39 
     40     private static final boolean TRACE = false;
     41 
     42     private final DataInputStream in;
     43 
     44     /**
     45      * By default we try to strictly validate rules followed by
     46      * our HprofWriter. For example, every end thread is preceded
     47      * by a matching start thread.
     48      */
     49     private boolean strict = true;
     50 
     51     /**
     52      * version string from header after read has been performed,
     53      * otherwise null. nullness used to detect if callers try to
     54      * access data before read is called.
     55      */
     56     private String version;
     57 
     58     private final Map<HprofData.StackTrace, int[]> stackTraces
     59             = new HashMap<HprofData.StackTrace, int[]>();
     60 
     61     private final HprofData hprofData = new HprofData(stackTraces);
     62 
     63     private final Map<Integer, String> idToString = new HashMap<Integer, String>();
     64     private final Map<Integer, String> idToClassName = new HashMap<Integer, String>();
     65     private final Map<Integer, StackTraceElement> idToStackFrame
     66             = new HashMap<Integer, StackTraceElement>();
     67     private final Map<Integer, HprofData.StackTrace> idToStackTrace
     68             = new HashMap<Integer, HprofData.StackTrace>();
     69 
     70     /**
     71      * Creates a BinaryHprofReader around the specified {@code
     72      * inputStream}
     73      */
     74     public BinaryHprofReader(InputStream inputStream) throws IOException {
     75         this.in = new DataInputStream(inputStream);
     76     }
     77 
     78     public boolean getStrict () {
     79         return strict;
     80     }
     81 
     82     public void setStrict (boolean strict) {
     83         if (version != null) {
     84             throw new IllegalStateException("cannot set strict after read()");
     85         }
     86         this.strict = strict;
     87     }
     88 
     89     /**
     90      * throws IllegalStateException if read() has not been called.
     91      */
     92     private void checkRead() {
     93         if (version == null) {
     94             throw new IllegalStateException("data access before read()");
     95         }
     96     }
     97 
     98     public String getVersion() {
     99         checkRead();
    100         return version;
    101     }
    102 
    103     public HprofData getHprofData() {
    104         checkRead();
    105         return hprofData;
    106     }
    107 
    108     /**
    109      * Read the hprof header and records from the input
    110      */
    111     public void read() throws IOException {
    112         parseHeader();
    113         parseRecords();
    114     }
    115 
    116     private void parseHeader() throws IOException {
    117         if (TRACE) {
    118             System.out.println("hprofTag=HEADER");
    119         }
    120         parseVersion();
    121         parseIdSize();
    122         parseTime();
    123     }
    124 
    125     private void parseVersion() throws IOException {
    126         String version = BinaryHprof.readMagic(in);
    127         if (version == null) {
    128             throw new MalformedHprofException("Could not find HPROF version");
    129         }
    130         if (TRACE) {
    131             System.out.println("\tversion=" + version);
    132         }
    133         this.version = version;
    134     }
    135 
    136     private void parseIdSize() throws IOException {
    137         int idSize = in.readInt();
    138         if (TRACE) {
    139             System.out.println("\tidSize=" + idSize);
    140         }
    141         if (idSize != BinaryHprof.ID_SIZE) {
    142             throw new MalformedHprofException("Unsupported identifier size: " + idSize);
    143         }
    144     }
    145 
    146     private void parseTime() throws IOException {
    147         long time = in.readLong();
    148         if (TRACE) {
    149             System.out.println("\ttime=" + Long.toHexString(time) + " " + new Date(time));
    150         }
    151         hprofData.setStartMillis(time);
    152     }
    153 
    154     private void parseRecords() throws IOException {
    155         while (parseRecord()) {
    156             ;
    157         }
    158     }
    159 
    160     /**
    161      * Read and process the next record. Returns true if a
    162      * record was handled, false on EOF.
    163      */
    164     private boolean parseRecord() throws IOException {
    165         int tagOrEOF = in.read();
    166         if (tagOrEOF == -1) {
    167             return false;
    168         }
    169         byte tag = (byte) tagOrEOF;
    170         int timeDeltaInMicroseconds = in.readInt();
    171         int recordLength = in.readInt();
    172         BinaryHprof.Tag hprofTag = BinaryHprof.Tag.get(tag);
    173         if (TRACE) {
    174             System.out.println("hprofTag=" + hprofTag);
    175         }
    176         if (hprofTag == null) {
    177             skipRecord(hprofTag, recordLength);
    178             return true;
    179         }
    180         String error = hprofTag.checkSize(recordLength);
    181         if (error != null) {
    182             throw new MalformedHprofException(error);
    183         }
    184         switch (hprofTag) {
    185             case CONTROL_SETTINGS:
    186                 parseControlSettings();
    187                 return true;
    188 
    189             case STRING_IN_UTF8:
    190                 parseStringInUtf8(recordLength);
    191                 return true;
    192 
    193             case START_THREAD:
    194                 parseStartThread();
    195                 return true;
    196             case END_THREAD:
    197                 parseEndThread();
    198                 return true;
    199 
    200             case LOAD_CLASS:
    201                 parseLoadClass();
    202                 return true;
    203             case STACK_FRAME:
    204                 parseStackFrame();
    205                 return true;
    206             case STACK_TRACE:
    207                 parseStackTrace(recordLength);
    208                 return true;
    209 
    210             case CPU_SAMPLES:
    211                 parseCpuSamples(recordLength);
    212                 return true;
    213 
    214             case UNLOAD_CLASS:
    215             case ALLOC_SITES:
    216             case HEAP_SUMMARY:
    217             case HEAP_DUMP:
    218             case HEAP_DUMP_SEGMENT:
    219             case HEAP_DUMP_END:
    220             default:
    221                 skipRecord(hprofTag, recordLength);
    222                 return true;
    223         }
    224     }
    225 
    226     private void skipRecord(BinaryHprof.Tag hprofTag, long recordLength) throws IOException {
    227         if (TRACE) {
    228             System.out.println("\tskipping recordLength=" + recordLength);
    229         }
    230         long skipped = in.skip(recordLength);
    231         if (skipped != recordLength) {
    232             throw new EOFException("Expected to skip " + recordLength
    233                                    + " bytes but only skipped " + skipped + " bytes");
    234         }
    235     }
    236 
    237     private void parseControlSettings() throws IOException {
    238         int flags = in.readInt();
    239         short depth = in.readShort();
    240         if (TRACE) {
    241             System.out.println("\tflags=" + Integer.toHexString(flags));
    242             System.out.println("\tdepth=" + depth);
    243         }
    244         hprofData.setFlags(flags);
    245         hprofData.setDepth(depth);
    246     }
    247 
    248     private void parseStringInUtf8(int recordLength) throws IOException {
    249         int stringId = in.readInt();
    250         byte[] bytes = new byte[recordLength - BinaryHprof.ID_SIZE];
    251         readFully(in, bytes);
    252         String string = new String(bytes, "UTF-8");
    253         if (TRACE) {
    254             System.out.println("\tstring=" + string);
    255         }
    256         String old = idToString.put(stringId, string);
    257         if (old != null) {
    258             throw new MalformedHprofException("Duplicate string id: " + stringId);
    259         }
    260     }
    261 
    262     private static void readFully(InputStream in, byte[] dst) throws IOException {
    263         int offset = 0;
    264         int byteCount = dst.length;
    265         while (byteCount > 0) {
    266             int bytesRead = in.read(dst, offset, byteCount);
    267             if (bytesRead < 0) {
    268                 throw new EOFException();
    269             }
    270             offset += bytesRead;
    271             byteCount -= bytesRead;
    272         }
    273     }
    274 
    275     private void parseLoadClass() throws IOException {
    276         int classId = in.readInt();
    277         int classObjectId = readId();
    278         // serial number apparently not a stack trace id. (int vs ID)
    279         // we don't use this field.
    280         int stackTraceSerialNumber = in.readInt();
    281         String className = readString();
    282         if (TRACE) {
    283             System.out.println("\tclassId=" + classId);
    284             System.out.println("\tclassObjectId=" + classObjectId);
    285             System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber);
    286             System.out.println("\tclassName=" + className);
    287         }
    288         String old = idToClassName.put(classId, className);
    289         if (old != null) {
    290             throw new MalformedHprofException("Duplicate class id: " + classId);
    291         }
    292     }
    293 
    294     private int readId() throws IOException {
    295         return in.readInt();
    296     }
    297 
    298     private String readString() throws IOException {
    299         int id = readId();
    300         if (id == 0) {
    301             return null;
    302         }
    303         String string = idToString.get(id);
    304         if (string == null) {
    305             throw new MalformedHprofException("Unknown string id " + id);
    306         }
    307         return string;
    308     }
    309 
    310     private String readClass() throws IOException {
    311         int id = readId();
    312         String string = idToClassName.get(id);
    313         if (string == null) {
    314             throw new MalformedHprofException("Unknown class id " + id);
    315         }
    316         return string;
    317     }
    318 
    319     private void parseStartThread() throws IOException {
    320         int threadId = in.readInt();
    321         int objectId = readId();
    322         // stack trace where thread was created.
    323         // serial number apparently not a stack trace id. (int vs ID)
    324         // we don't use this field.
    325         int stackTraceSerialNumber = in.readInt();
    326         String threadName = readString();
    327         String groupName = readString();
    328         String parentGroupName = readString();
    329         if (TRACE) {
    330             System.out.println("\tthreadId=" + threadId);
    331             System.out.println("\tobjectId=" + objectId);
    332             System.out.println("\tstackTraceSerialNumber=" + stackTraceSerialNumber);
    333             System.out.println("\tthreadName=" + threadName);
    334             System.out.println("\tgroupName=" + groupName);
    335             System.out.println("\tparentGroupName=" + parentGroupName);
    336         }
    337         HprofData.ThreadEvent event
    338                 = HprofData.ThreadEvent.start(objectId, threadId,
    339                                               threadName, groupName, parentGroupName);
    340         hprofData.addThreadEvent(event);
    341     }
    342 
    343     private void parseEndThread() throws IOException {
    344         int threadId = in.readInt();
    345         if (TRACE) {
    346             System.out.println("\tthreadId=" + threadId);
    347         }
    348         HprofData.ThreadEvent event = HprofData.ThreadEvent.end(threadId);
    349         hprofData.addThreadEvent(event);
    350     }
    351 
    352     private void parseStackFrame() throws IOException {
    353         int stackFrameId = readId();
    354         String methodName = readString();
    355         String methodSignature = readString();
    356         String file = readString();
    357         String className = readClass();
    358         int line = in.readInt();
    359         if (TRACE) {
    360             System.out.println("\tstackFrameId=" + stackFrameId);
    361             System.out.println("\tclassName=" + className);
    362             System.out.println("\tmethodName=" + methodName);
    363             System.out.println("\tmethodSignature=" + methodSignature);
    364             System.out.println("\tfile=" + file);
    365             System.out.println("\tline=" + line);
    366         }
    367         StackTraceElement stackFrame = new StackTraceElement(className, methodName, file, line);
    368         StackTraceElement old = idToStackFrame.put(stackFrameId, stackFrame);
    369         if (old != null) {
    370             throw new MalformedHprofException("Duplicate stack frame id: " + stackFrameId);
    371         }
    372     }
    373 
    374     private void parseStackTrace(int recordLength) throws IOException {
    375         int stackTraceId = in.readInt();
    376         int threadId = in.readInt();
    377         int frames = in.readInt();
    378         if (TRACE) {
    379             System.out.println("\tstackTraceId=" + stackTraceId);
    380             System.out.println("\tthreadId=" + threadId);
    381             System.out.println("\tframes=" + frames);
    382         }
    383         int expectedLength = 4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE);
    384         if (recordLength != expectedLength) {
    385             throw new MalformedHprofException("Expected stack trace record of size "
    386                                               + expectedLength
    387                                               + " based on number of frames but header "
    388                                               + "specified a length of  " + recordLength);
    389         }
    390         StackTraceElement[] stackFrames = new StackTraceElement[frames];
    391         for (int i = 0; i < frames; i++) {
    392             int stackFrameId = readId();
    393             StackTraceElement stackFrame = idToStackFrame.get(stackFrameId);
    394             if (TRACE) {
    395                 System.out.println("\tstackFrameId=" + stackFrameId);
    396                 System.out.println("\tstackFrame=" + stackFrame);
    397             }
    398             if (stackFrame == null) {
    399                 throw new MalformedHprofException("Unknown stack frame id " + stackFrameId);
    400             }
    401             stackFrames[i] = stackFrame;
    402         }
    403 
    404         HprofData.StackTrace stackTrace
    405                 = new HprofData.StackTrace(stackTraceId, threadId, stackFrames);
    406         if (strict) {
    407             hprofData.addStackTrace(stackTrace, new int[1]);
    408         } else {
    409             // The RI can have duplicate stacks, presumably they
    410             // have a minor race if two samples with the same
    411             // stack are taken around the same time. if we have a
    412             // duplicate, just skip adding it to hprofData, but
    413             // register it locally in idToStackFrame. if it seen
    414             // in CPU_SAMPLES, we will find a StackTrace is equal
    415             // to the first, so they will share a countCell.
    416             int[] countCell = stackTraces.get(stackTrace);
    417             if (countCell == null) {
    418                 hprofData.addStackTrace(stackTrace, new int[1]);
    419             }
    420         }
    421 
    422         HprofData.StackTrace old = idToStackTrace.put(stackTraceId, stackTrace);
    423         if (old != null) {
    424             throw new MalformedHprofException("Duplicate stack trace id: " + stackTraceId);
    425         }
    426 
    427     }
    428 
    429     private void parseCpuSamples(int recordLength) throws IOException {
    430         int totalSamples = in.readInt();
    431         int samplesCount = in.readInt();
    432         if (TRACE) {
    433             System.out.println("\ttotalSamples=" + totalSamples);
    434             System.out.println("\tsamplesCount=" + samplesCount);
    435         }
    436         int expectedLength = 4 + 4 + (samplesCount * (4 + 4));
    437         if (recordLength != expectedLength) {
    438             throw new MalformedHprofException("Expected CPU samples record of size "
    439                                               + expectedLength
    440                                               + " based on number of samples but header "
    441                                               + "specified a length of  " + recordLength);
    442         }
    443         int total = 0;
    444         for (int i = 0; i < samplesCount; i++) {
    445             int count = in.readInt();
    446             int stackTraceId = in.readInt();
    447             if (TRACE) {
    448                 System.out.println("\tcount=" + count);
    449                 System.out.println("\tstackTraceId=" + stackTraceId);
    450             }
    451             HprofData.StackTrace stackTrace = idToStackTrace.get(stackTraceId);
    452             if (stackTrace == null) {
    453                 throw new MalformedHprofException("Unknown stack trace id " + stackTraceId);
    454             }
    455             if (count == 0) {
    456                 throw new MalformedHprofException("Zero sample count for stack trace "
    457                                                   + stackTrace);
    458             }
    459             int[] countCell = stackTraces.get(stackTrace);
    460             if (strict) {
    461                 if (countCell[0] != 0) {
    462                     throw new MalformedHprofException("Setting sample count of stack trace "
    463                                                       + stackTrace + " to " + count
    464                                                       + " found it was already initialized to "
    465                                                       + countCell[0]);
    466                 }
    467             } else {
    468                 // Coalesce counts from duplicate stack traces.
    469                 // For more on this, see comments in parseStackTrace.
    470                 count += countCell[0];
    471             }
    472             countCell[0] = count;
    473             total += count;
    474         }
    475         if (strict && totalSamples != total) {
    476             throw new MalformedHprofException("Expected a total of " + totalSamples
    477                                               + " samples but saw " + total);
    478         }
    479     }
    480 }
    481