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      */
BinaryHprofReader(InputStream inputStream)74     public BinaryHprofReader(InputStream inputStream) throws IOException {
75         this.in = new DataInputStream(inputStream);
76     }
77 
getStrict()78     public boolean getStrict () {
79         return strict;
80     }
81 
setStrict(boolean strict)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      */
checkRead()92     private void checkRead() {
93         if (version == null) {
94             throw new IllegalStateException("data access before read()");
95         }
96     }
97 
getVersion()98     public String getVersion() {
99         checkRead();
100         return version;
101     }
102 
getHprofData()103     public HprofData getHprofData() {
104         checkRead();
105         return hprofData;
106     }
107 
108     /**
109      * Read the hprof header and records from the input
110      */
read()111     public void read() throws IOException {
112         parseHeader();
113         parseRecords();
114     }
115 
parseHeader()116     private void parseHeader() throws IOException {
117         if (TRACE) {
118             System.out.println("hprofTag=HEADER");
119         }
120         parseVersion();
121         parseIdSize();
122         parseTime();
123     }
124 
parseVersion()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 
parseIdSize()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 
parseTime()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 
parseRecords()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      */
parseRecord()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 
skipRecord(BinaryHprof.Tag hprofTag, long recordLength)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 
parseControlSettings()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 
parseStringInUtf8(int recordLength)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 
readFully(InputStream in, byte[] dst)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 
parseLoadClass()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 
readId()294     private int readId() throws IOException {
295         return in.readInt();
296     }
297 
readString()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 
readClass()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 
parseStartThread()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 
parseEndThread()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 
parseStackFrame()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 
parseStackTrace(int recordLength)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 
parseCpuSamples(int recordLength)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