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      */
write(HprofData data, OutputStream outputStream)47     public static void write(HprofData data, OutputStream outputStream) throws IOException {
48         new BinaryHprofWriter(data, outputStream).write();
49     }
50 
BinaryHprofWriter(HprofData data, OutputStream outputStream)51     private BinaryHprofWriter(HprofData data, OutputStream outputStream) {
52         this.data = data;
53         this.out = new DataOutputStream(outputStream);
54     }
55 
write()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 
writeHeader(long dumpTimeInMilliseconds)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 
writeControlSettings(int flags, int depth)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 
writeThreadEvent(HprofData.ThreadEvent e)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 
writeStartThread(HprofData.ThreadEvent e)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 
writeStopThread(HprofData.ThreadEvent e)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 
writeRecordHeader(BinaryHprof.Tag hprofTag, int timeDeltaInMicroseconds, int recordLength)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 
writeId(int id)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      */
writeString(String string)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 
writeCpuSamples(int totalSamples, Set<HprofData.Sample> samples)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 
writeStackTrace(HprofData.StackTrace stackTrace)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 
writeLoadClass(String className)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 
writeStackFrame(StackTraceElement stackFrame)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