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