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.DataOutputStream;
     20 import java.io.IOException;
     21 import java.io.OutputStream;
     22 import java.util.HashMap;
     23 import java.util.Map;
     24 import java.util.Set;
     25 
     26 /**
     27  * BinaryHprofWriter produces hprof compatible binary output for use
     28  * with third party tools. Such files can be converted to text with
     29  * with {@link HprofBinaryToAscii} or read back in with {@link BinaryHprofReader}.
     30  */
     31 public final class BinaryHprofWriter {
     32 
     33     private int nextStringId = 1; // id 0 => null
     34     private int nextClassId = 1;
     35     private int nextStackFrameId = 1;
     36     private final Map<String, Integer> stringToId = new HashMap<String, Integer>();
     37     private final Map<String, Integer> classNameToId = new HashMap<String, Integer>();
     38     private final Map<StackTraceElement, Integer> stackFrameToId
     39             = new HashMap<StackTraceElement, Integer>();
     40 
     41     private final HprofData data;
     42     private final DataOutputStream out;
     43 
     44     /**
     45      * Writes the provided data to the specified stream.
     46      */
     47     public static void write(HprofData data, OutputStream outputStream) throws IOException {
     48         new BinaryHprofWriter(data, outputStream).write();
     49     }
     50 
     51     private BinaryHprofWriter(HprofData data, OutputStream outputStream) {
     52         this.data = data;
     53         this.out = new DataOutputStream(outputStream);
     54     }
     55 
     56     private void write() throws IOException {
     57         try {
     58             writeHeader(data.getStartMillis());
     59 
     60             writeControlSettings(data.getFlags(), data.getDepth());
     61 
     62             for (HprofData.ThreadEvent event : data.getThreadHistory()) {
     63                 writeThreadEvent(event);
     64             }
     65 
     66             Set<HprofData.Sample> samples = data.getSamples();
     67             int total = 0;
     68             for (HprofData.Sample sample : samples) {
     69                 total += sample.count;
     70                 writeStackTrace(sample.stackTrace);
     71             }
     72             writeCpuSamples(total, samples);
     73 
     74         } finally {
     75             out.flush();
     76         }
     77     }
     78 
     79     private void writeHeader(long dumpTimeInMilliseconds) throws IOException {
     80         out.writeBytes(BinaryHprof.MAGIC + "1.0.2");
     81         out.writeByte(0); // null terminated string
     82         out.writeInt(BinaryHprof.ID_SIZE);
     83         out.writeLong(dumpTimeInMilliseconds);
     84     }
     85 
     86     private void writeControlSettings(int flags, int depth) throws IOException {
     87         if (depth > Short.MAX_VALUE) {
     88             throw new IllegalArgumentException("depth too large for binary hprof: "
     89                                                + depth + " > " + Short.MAX_VALUE);
     90         }
     91         writeRecordHeader(BinaryHprof.Tag.CONTROL_SETTINGS,
     92                           0,
     93                           BinaryHprof.Tag.CONTROL_SETTINGS.maximumSize);
     94         out.writeInt(flags);
     95         out.writeShort((short) depth);
     96     }
     97 
     98     private void writeThreadEvent(HprofData.ThreadEvent e) throws IOException {
     99         switch (e.type) {
    100             case START:
    101                 writeStartThread(e);
    102                 return;
    103             case END:
    104                 writeStopThread(e);
    105                 return;
    106         }
    107         throw new IllegalStateException(e.type.toString());
    108     }
    109 
    110     private void writeStartThread(HprofData.ThreadEvent e) throws IOException {
    111         int threadNameId = writeString(e.threadName);
    112         int groupNameId = writeString(e.groupName);
    113         int parentGroupNameId = writeString(e.parentGroupName);
    114         writeRecordHeader(BinaryHprof.Tag.START_THREAD,
    115                           0,
    116                           BinaryHprof.Tag.START_THREAD.maximumSize);
    117         out.writeInt(e.threadId);
    118         writeId(e.objectId);
    119         out.writeInt(0); // stack trace where thread was started unavailable
    120         writeId(threadNameId);
    121         writeId(groupNameId);
    122         writeId(parentGroupNameId);
    123     }
    124 
    125     private void writeStopThread(HprofData.ThreadEvent e) throws IOException {
    126         writeRecordHeader(BinaryHprof.Tag.END_THREAD,
    127                           0,
    128                           BinaryHprof.Tag.END_THREAD.maximumSize);
    129         out.writeInt(e.threadId);
    130     }
    131 
    132     private void writeRecordHeader(BinaryHprof.Tag hprofTag,
    133                                    int timeDeltaInMicroseconds,
    134                                    int recordLength) throws IOException {
    135         String error = hprofTag.checkSize(recordLength);
    136         if (error != null) {
    137             throw new AssertionError(error);
    138         }
    139         out.writeByte(hprofTag.tag);
    140         out.writeInt(timeDeltaInMicroseconds);
    141         out.writeInt(recordLength);
    142     }
    143 
    144     private void writeId(int id) throws IOException {
    145         out.writeInt(id);
    146     }
    147 
    148     /**
    149      * Ensures that a string has been writen to the out and
    150      * returns its ID. The ID of a null string is zero, and
    151      * doesn't actually result in any output. In a string has
    152      * already been written previously, the earlier ID will be
    153      * returned and no output will be written.
    154      */
    155     private int writeString(String string) throws IOException {
    156         if (string == null) {
    157             return 0;
    158         }
    159         Integer identifier = stringToId.get(string);
    160         if (identifier != null) {
    161             return identifier;
    162         }
    163 
    164         int id = nextStringId++;
    165         stringToId.put(string, id);
    166 
    167         byte[] bytes = string.getBytes("UTF-8");
    168         writeRecordHeader(BinaryHprof.Tag.STRING_IN_UTF8,
    169                           0,
    170                           BinaryHprof.ID_SIZE + bytes.length);
    171         out.writeInt(id);
    172         out.write(bytes, 0, bytes.length);
    173 
    174         return id;
    175     }
    176 
    177     private void writeCpuSamples(int totalSamples, Set<HprofData.Sample> samples)
    178             throws IOException {
    179         int samplesCount = samples.size();
    180         if (samplesCount == 0) {
    181             return;
    182         }
    183         writeRecordHeader(BinaryHprof.Tag.CPU_SAMPLES, 0, 4 + 4 + (samplesCount * (4 + 4)));
    184         out.writeInt(totalSamples);
    185         out.writeInt(samplesCount);
    186         for (HprofData.Sample sample : samples) {
    187             out.writeInt(sample.count);
    188             out.writeInt(sample.stackTrace.stackTraceId);
    189         }
    190     }
    191 
    192     private void writeStackTrace(HprofData.StackTrace stackTrace) throws IOException {
    193         int frames = stackTrace.stackFrames.length;
    194         int[] stackFrameIds = new int[frames];
    195         for (int i = 0; i < frames; i++) {
    196             stackFrameIds[i] = writeStackFrame(stackTrace.stackFrames[i]);
    197         }
    198         writeRecordHeader(BinaryHprof.Tag.STACK_TRACE,
    199                           0,
    200                           4 + 4 + 4 + (frames * BinaryHprof.ID_SIZE));
    201         out.writeInt(stackTrace.stackTraceId);
    202         out.writeInt(stackTrace.threadId);
    203         out.writeInt(frames);
    204         for (int stackFrameId : stackFrameIds) {
    205             writeId(stackFrameId);
    206         }
    207     }
    208 
    209     private int writeLoadClass(String className) throws IOException {
    210         Integer identifier = classNameToId.get(className);
    211         if (identifier != null) {
    212             return identifier;
    213         }
    214         int id = nextClassId++;
    215         classNameToId.put(className, id);
    216 
    217         int classNameId = writeString(className);
    218         writeRecordHeader(BinaryHprof.Tag.LOAD_CLASS,
    219                           0,
    220                           BinaryHprof.Tag.LOAD_CLASS.maximumSize);
    221         out.writeInt(id);
    222         writeId(0); // class object ID
    223         out.writeInt(0); // stack trace where class was loaded is unavailable
    224         writeId(classNameId);
    225 
    226         return id;
    227     }
    228 
    229     private int writeStackFrame(StackTraceElement stackFrame) throws IOException {
    230         Integer identifier = stackFrameToId.get(stackFrame);
    231         if (identifier != null) {
    232             return identifier;
    233         }
    234 
    235         int id = nextStackFrameId++;
    236         stackFrameToId.put(stackFrame, id);
    237 
    238         int classId = writeLoadClass(stackFrame.getClassName());
    239         int methodNameId = writeString(stackFrame.getMethodName());
    240         int sourceId = writeString(stackFrame.getFileName());
    241         writeRecordHeader(BinaryHprof.Tag.STACK_FRAME,
    242                           0,
    243                           BinaryHprof.Tag.STACK_FRAME.maximumSize);
    244         writeId(id);
    245         writeId(methodNameId);
    246         writeId(0); // method signature is unavailable from StackTraceElement
    247         writeId(sourceId);
    248         out.writeInt(classId);
    249         out.writeInt(stackFrame.getLineNumber());
    250 
    251         return id;
    252     }
    253 }
    254