1 /* 2 * Copyright (C) 2007 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 com.android.ddmlib; 18 19 import com.android.ddmlib.ClientData.AllocationTrackingStatus; 20 import com.android.ddmlib.ClientData.IHprofDumpHandler; 21 22 import java.io.IOException; 23 import java.nio.BufferUnderflowException; 24 import java.nio.ByteBuffer; 25 import java.util.ArrayList; 26 import java.util.Collections; 27 28 /** 29 * Handle heap status updates. 30 */ 31 final class HandleHeap extends ChunkHandler { 32 33 public static final int CHUNK_HPIF = type("HPIF"); 34 public static final int CHUNK_HPST = type("HPST"); 35 public static final int CHUNK_HPEN = type("HPEN"); 36 public static final int CHUNK_HPSG = type("HPSG"); 37 public static final int CHUNK_HPGC = type("HPGC"); 38 public static final int CHUNK_HPDU = type("HPDU"); 39 public static final int CHUNK_HPDS = type("HPDS"); 40 public static final int CHUNK_REAE = type("REAE"); 41 public static final int CHUNK_REAQ = type("REAQ"); 42 public static final int CHUNK_REAL = type("REAL"); 43 44 // args to sendHPSG 45 public static final int WHEN_DISABLE = 0; 46 public static final int WHEN_GC = 1; 47 public static final int WHAT_MERGE = 0; // merge adjacent objects 48 public static final int WHAT_OBJ = 1; // keep objects distinct 49 50 // args to sendHPIF 51 public static final int HPIF_WHEN_NEVER = 0; 52 public static final int HPIF_WHEN_NOW = 1; 53 public static final int HPIF_WHEN_NEXT_GC = 2; 54 public static final int HPIF_WHEN_EVERY_GC = 3; 55 56 private static final HandleHeap mInst = new HandleHeap(); 57 HandleHeap()58 private HandleHeap() {} 59 60 /** 61 * Register for the packets we expect to get from the client. 62 */ register(MonitorThread mt)63 public static void register(MonitorThread mt) { 64 mt.registerChunkHandler(CHUNK_HPIF, mInst); 65 mt.registerChunkHandler(CHUNK_HPST, mInst); 66 mt.registerChunkHandler(CHUNK_HPEN, mInst); 67 mt.registerChunkHandler(CHUNK_HPSG, mInst); 68 mt.registerChunkHandler(CHUNK_HPDS, mInst); 69 mt.registerChunkHandler(CHUNK_REAQ, mInst); 70 mt.registerChunkHandler(CHUNK_REAL, mInst); 71 } 72 73 /** 74 * Client is ready. 75 */ 76 @Override clientReady(Client client)77 public void clientReady(Client client) throws IOException { 78 if (client.isHeapUpdateEnabled()) { 79 //sendHPSG(client, WHEN_GC, WHAT_MERGE); 80 sendHPIF(client, HPIF_WHEN_EVERY_GC); 81 } 82 } 83 84 /** 85 * Client went away. 86 */ 87 @Override clientDisconnected(Client client)88 public void clientDisconnected(Client client) {} 89 90 /** 91 * Chunk handler entry point. 92 */ 93 @Override handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId)94 public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) { 95 Log.d("ddm-heap", "handling " + ChunkHandler.name(type)); 96 97 if (type == CHUNK_HPIF) { 98 handleHPIF(client, data); 99 } else if (type == CHUNK_HPST) { 100 handleHPST(client, data); 101 } else if (type == CHUNK_HPEN) { 102 handleHPEN(client, data); 103 } else if (type == CHUNK_HPSG) { 104 handleHPSG(client, data); 105 } else if (type == CHUNK_HPDU) { 106 handleHPDU(client, data); 107 } else if (type == CHUNK_HPDS) { 108 handleHPDS(client, data); 109 } else if (type == CHUNK_REAQ) { 110 handleREAQ(client, data); 111 } else if (type == CHUNK_REAL) { 112 handleREAL(client, data); 113 } else { 114 handleUnknownChunk(client, type, data, isReply, msgId); 115 } 116 } 117 118 /* 119 * Handle a heap info message. 120 */ handleHPIF(Client client, ByteBuffer data)121 private void handleHPIF(Client client, ByteBuffer data) { 122 Log.d("ddm-heap", "HPIF!"); 123 try { 124 int numHeaps = data.getInt(); 125 126 for (int i = 0; i < numHeaps; i++) { 127 int heapId = data.getInt(); 128 @SuppressWarnings("unused") 129 long timeStamp = data.getLong(); 130 @SuppressWarnings("unused") 131 byte reason = data.get(); 132 long maxHeapSize = (long)data.getInt() & 0x00ffffffff; 133 long heapSize = (long)data.getInt() & 0x00ffffffff; 134 long bytesAllocated = (long)data.getInt() & 0x00ffffffff; 135 long objectsAllocated = (long)data.getInt() & 0x00ffffffff; 136 137 client.getClientData().setHeapInfo(heapId, maxHeapSize, 138 heapSize, bytesAllocated, objectsAllocated); 139 client.update(Client.CHANGE_HEAP_DATA); 140 } 141 } catch (BufferUnderflowException ex) { 142 Log.w("ddm-heap", "malformed HPIF chunk from client"); 143 } 144 } 145 146 /** 147 * Send an HPIF (HeaP InFo) request to the client. 148 */ sendHPIF(Client client, int when)149 public static void sendHPIF(Client client, int when) throws IOException { 150 ByteBuffer rawBuf = allocBuffer(1); 151 JdwpPacket packet = new JdwpPacket(rawBuf); 152 ByteBuffer buf = getChunkDataBuf(rawBuf); 153 154 buf.put((byte)when); 155 156 finishChunkPacket(packet, CHUNK_HPIF, buf.position()); 157 Log.d("ddm-heap", "Sending " + name(CHUNK_HPIF) + ": when=" + when); 158 client.sendAndConsume(packet, mInst); 159 } 160 161 /* 162 * Handle a heap segment series start message. 163 */ handleHPST(Client client, ByteBuffer data)164 private void handleHPST(Client client, ByteBuffer data) { 165 /* Clear out any data that's sitting around to 166 * get ready for the chunks that are about to come. 167 */ 168 //xxx todo: only clear data that belongs to the heap mentioned in <data>. 169 client.getClientData().getVmHeapData().clearHeapData(); 170 } 171 172 /* 173 * Handle a heap segment series end message. 174 */ handleHPEN(Client client, ByteBuffer data)175 private void handleHPEN(Client client, ByteBuffer data) { 176 /* Let the UI know that we've received all of the 177 * data for this heap. 178 */ 179 //xxx todo: only seal data that belongs to the heap mentioned in <data>. 180 client.getClientData().getVmHeapData().sealHeapData(); 181 client.update(Client.CHANGE_HEAP_DATA); 182 } 183 184 /* 185 * Handle a heap segment message. 186 */ handleHPSG(Client client, ByteBuffer data)187 private void handleHPSG(Client client, ByteBuffer data) { 188 byte dataCopy[] = new byte[data.limit()]; 189 data.rewind(); 190 data.get(dataCopy); 191 data = ByteBuffer.wrap(dataCopy); 192 client.getClientData().getVmHeapData().addHeapData(data); 193 //xxx todo: add to the heap mentioned in <data> 194 } 195 196 /** 197 * Sends an HPSG (HeaP SeGment) request to the client. 198 */ sendHPSG(Client client, int when, int what)199 public static void sendHPSG(Client client, int when, int what) 200 throws IOException { 201 202 ByteBuffer rawBuf = allocBuffer(2); 203 JdwpPacket packet = new JdwpPacket(rawBuf); 204 ByteBuffer buf = getChunkDataBuf(rawBuf); 205 206 buf.put((byte)when); 207 buf.put((byte)what); 208 209 finishChunkPacket(packet, CHUNK_HPSG, buf.position()); 210 Log.d("ddm-heap", "Sending " + name(CHUNK_HPSG) + ": when=" 211 + when + ", what=" + what); 212 client.sendAndConsume(packet, mInst); 213 } 214 215 /** 216 * Sends an HPGC request to the client. 217 */ sendHPGC(Client client)218 public static void sendHPGC(Client client) 219 throws IOException { 220 ByteBuffer rawBuf = allocBuffer(0); 221 JdwpPacket packet = new JdwpPacket(rawBuf); 222 ByteBuffer buf = getChunkDataBuf(rawBuf); 223 224 // no data 225 226 finishChunkPacket(packet, CHUNK_HPGC, buf.position()); 227 Log.d("ddm-heap", "Sending " + name(CHUNK_HPGC)); 228 client.sendAndConsume(packet, mInst); 229 } 230 231 /** 232 * Sends an HPDU request to the client. 233 * 234 * We will get an HPDU response when the heap dump has completed. On 235 * failure we get a generic failure response. 236 * 237 * @param fileName name of output file (on device) 238 */ sendHPDU(Client client, String fileName)239 public static void sendHPDU(Client client, String fileName) 240 throws IOException { 241 ByteBuffer rawBuf = allocBuffer(4 + fileName.length() * 2); 242 JdwpPacket packet = new JdwpPacket(rawBuf); 243 ByteBuffer buf = getChunkDataBuf(rawBuf); 244 245 buf.putInt(fileName.length()); 246 putString(buf, fileName); 247 248 finishChunkPacket(packet, CHUNK_HPDU, buf.position()); 249 Log.d("ddm-heap", "Sending " + name(CHUNK_HPDU) + " '" + fileName +"'"); 250 client.sendAndConsume(packet, mInst); 251 client.getClientData().setPendingHprofDump(fileName); 252 } 253 254 /** 255 * Sends an HPDS request to the client. 256 * 257 * We will get an HPDS response when the heap dump has completed. On 258 * failure we get a generic failure response. 259 * 260 * This is more expensive for the device than HPDU, because the entire 261 * heap dump is held in RAM instead of spooled out to a temp file. On 262 * the other hand, permission to write to /sdcard is not required. 263 * 264 * @param fileName name of output file (on device) 265 */ sendHPDS(Client client)266 public static void sendHPDS(Client client) 267 throws IOException { 268 ByteBuffer rawBuf = allocBuffer(0); 269 JdwpPacket packet = new JdwpPacket(rawBuf); 270 ByteBuffer buf = getChunkDataBuf(rawBuf); 271 272 finishChunkPacket(packet, CHUNK_HPDS, buf.position()); 273 Log.d("ddm-heap", "Sending " + name(CHUNK_HPDS)); 274 client.sendAndConsume(packet, mInst); 275 } 276 277 /* 278 * Handle notification of completion of a HeaP DUmp. 279 */ handleHPDU(Client client, ByteBuffer data)280 private void handleHPDU(Client client, ByteBuffer data) { 281 byte result; 282 283 // get the filename and make the client not have pending HPROF dump anymore. 284 String filename = client.getClientData().getPendingHprofDump(); 285 client.getClientData().setPendingHprofDump(null); 286 287 // get the dump result 288 result = data.get(); 289 290 // get the app-level handler for HPROF dump 291 IHprofDumpHandler handler = ClientData.getHprofDumpHandler(); 292 if (handler != null) { 293 if (result == 0) { 294 handler.onSuccess(filename, client); 295 296 Log.d("ddm-heap", "Heap dump request has finished"); 297 } else { 298 handler.onEndFailure(client, null); 299 Log.w("ddm-heap", "Heap dump request failed (check device log)"); 300 } 301 } 302 } 303 304 /* 305 * Handle HeaP Dump Streaming response. "data" contains the full 306 * hprof dump. 307 */ handleHPDS(Client client, ByteBuffer data)308 private void handleHPDS(Client client, ByteBuffer data) { 309 IHprofDumpHandler handler = ClientData.getHprofDumpHandler(); 310 if (handler != null) { 311 byte[] stuff = new byte[data.capacity()]; 312 data.get(stuff, 0, stuff.length); 313 314 Log.d("ddm-hprof", "got hprof file, size: " + data.capacity() + " bytes"); 315 316 handler.onSuccess(stuff, client); 317 } 318 } 319 320 /** 321 * Sends a REAE (REcent Allocation Enable) request to the client. 322 */ sendREAE(Client client, boolean enable)323 public static void sendREAE(Client client, boolean enable) 324 throws IOException { 325 ByteBuffer rawBuf = allocBuffer(1); 326 JdwpPacket packet = new JdwpPacket(rawBuf); 327 ByteBuffer buf = getChunkDataBuf(rawBuf); 328 329 buf.put((byte) (enable ? 1 : 0)); 330 331 finishChunkPacket(packet, CHUNK_REAE, buf.position()); 332 Log.d("ddm-heap", "Sending " + name(CHUNK_REAE) + ": " + enable); 333 client.sendAndConsume(packet, mInst); 334 } 335 336 /** 337 * Sends a REAQ (REcent Allocation Query) request to the client. 338 */ sendREAQ(Client client)339 public static void sendREAQ(Client client) 340 throws IOException { 341 ByteBuffer rawBuf = allocBuffer(0); 342 JdwpPacket packet = new JdwpPacket(rawBuf); 343 ByteBuffer buf = getChunkDataBuf(rawBuf); 344 345 // no data 346 347 finishChunkPacket(packet, CHUNK_REAQ, buf.position()); 348 Log.d("ddm-heap", "Sending " + name(CHUNK_REAQ)); 349 client.sendAndConsume(packet, mInst); 350 } 351 352 /** 353 * Sends a REAL (REcent ALlocation) request to the client. 354 */ sendREAL(Client client)355 public static void sendREAL(Client client) 356 throws IOException { 357 ByteBuffer rawBuf = allocBuffer(0); 358 JdwpPacket packet = new JdwpPacket(rawBuf); 359 ByteBuffer buf = getChunkDataBuf(rawBuf); 360 361 // no data 362 363 finishChunkPacket(packet, CHUNK_REAL, buf.position()); 364 Log.d("ddm-heap", "Sending " + name(CHUNK_REAL)); 365 client.sendAndConsume(packet, mInst); 366 } 367 368 /* 369 * Handle the response from our REcent Allocation Query message. 370 */ handleREAQ(Client client, ByteBuffer data)371 private void handleREAQ(Client client, ByteBuffer data) { 372 boolean enabled; 373 374 enabled = (data.get() != 0); 375 Log.d("ddm-heap", "REAQ says: enabled=" + enabled); 376 377 client.getClientData().setAllocationStatus(enabled ? 378 AllocationTrackingStatus.ON : AllocationTrackingStatus.OFF); 379 client.update(Client.CHANGE_HEAP_ALLOCATION_STATUS); 380 } 381 382 /** 383 * Converts a VM class descriptor string ("Landroid/os/Debug;") to 384 * a dot-notation class name ("android.os.Debug"). 385 */ descriptorToDot(String str)386 private String descriptorToDot(String str) { 387 // count the number of arrays. 388 int array = 0; 389 while (str.startsWith("[")) { 390 str = str.substring(1); 391 array++; 392 } 393 394 int len = str.length(); 395 396 /* strip off leading 'L' and trailing ';' if appropriate */ 397 if (len >= 2 && str.charAt(0) == 'L' && str.charAt(len - 1) == ';') { 398 str = str.substring(1, len-1); 399 str = str.replace('/', '.'); 400 } else { 401 // convert the basic types 402 if ("C".equals(str)) { 403 str = "char"; 404 } else if ("B".equals(str)) { 405 str = "byte"; 406 } else if ("Z".equals(str)) { 407 str = "boolean"; 408 } else if ("S".equals(str)) { 409 str = "short"; 410 } else if ("I".equals(str)) { 411 str = "int"; 412 } else if ("J".equals(str)) { 413 str = "long"; 414 } else if ("F".equals(str)) { 415 str = "float"; 416 } else if ("D".equals(str)) { 417 str = "double"; 418 } 419 } 420 421 // now add the array part 422 for (int a = 0 ; a < array; a++) { 423 str = str + "[]"; 424 } 425 426 return str; 427 } 428 429 /** 430 * Reads a string table out of "data". 431 * 432 * This is just a serial collection of strings, each of which is a 433 * four-byte length followed by UTF-16 data. 434 */ readStringTable(ByteBuffer data, String[] strings)435 private void readStringTable(ByteBuffer data, String[] strings) { 436 int count = strings.length; 437 int i; 438 439 for (i = 0; i < count; i++) { 440 int nameLen = data.getInt(); 441 String descriptor = getString(data, nameLen); 442 strings[i] = descriptorToDot(descriptor); 443 } 444 } 445 446 /* 447 * Handle a REcent ALlocation response. 448 * 449 * Message header (all values big-endian): 450 * (1b) message header len (to allow future expansion); includes itself 451 * (1b) entry header len 452 * (1b) stack frame len 453 * (2b) number of entries 454 * (4b) offset to string table from start of message 455 * (2b) number of class name strings 456 * (2b) number of method name strings 457 * (2b) number of source file name strings 458 * For each entry: 459 * (4b) total allocation size 460 * (2b) threadId 461 * (2b) allocated object's class name index 462 * (1b) stack depth 463 * For each stack frame: 464 * (2b) method's class name 465 * (2b) method name 466 * (2b) method source file 467 * (2b) line number, clipped to 32767; -2 if native; -1 if no source 468 * (xb) class name strings 469 * (xb) method name strings 470 * (xb) source file strings 471 * 472 * As with other DDM traffic, strings are sent as a 4-byte length 473 * followed by UTF-16 data. 474 */ handleREAL(Client client, ByteBuffer data)475 private void handleREAL(Client client, ByteBuffer data) { 476 Log.e("ddm-heap", "*** Received " + name(CHUNK_REAL)); 477 int messageHdrLen, entryHdrLen, stackFrameLen; 478 int numEntries, offsetToStrings; 479 int numClassNames, numMethodNames, numFileNames; 480 481 /* 482 * Read the header. 483 */ 484 messageHdrLen = (data.get() & 0xff); 485 entryHdrLen = (data.get() & 0xff); 486 stackFrameLen = (data.get() & 0xff); 487 numEntries = (data.getShort() & 0xffff); 488 offsetToStrings = data.getInt(); 489 numClassNames = (data.getShort() & 0xffff); 490 numMethodNames = (data.getShort() & 0xffff); 491 numFileNames = (data.getShort() & 0xffff); 492 493 494 /* 495 * Skip forward to the strings and read them. 496 */ 497 data.position(offsetToStrings); 498 499 String[] classNames = new String[numClassNames]; 500 String[] methodNames = new String[numMethodNames]; 501 String[] fileNames = new String[numFileNames]; 502 503 readStringTable(data, classNames); 504 readStringTable(data, methodNames); 505 //System.out.println("METHODS: " 506 // + java.util.Arrays.deepToString(methodNames)); 507 readStringTable(data, fileNames); 508 509 /* 510 * Skip back to a point just past the header and start reading 511 * entries. 512 */ 513 data.position(messageHdrLen); 514 515 ArrayList<AllocationInfo> list = new ArrayList<AllocationInfo>(numEntries); 516 for (int i = 0; i < numEntries; i++) { 517 int totalSize; 518 int threadId, classNameIndex, stackDepth; 519 520 totalSize = data.getInt(); 521 threadId = (data.getShort() & 0xffff); 522 classNameIndex = (data.getShort() & 0xffff); 523 stackDepth = (data.get() & 0xff); 524 /* we've consumed 9 bytes; gobble up any extra */ 525 for (int skip = 9; skip < entryHdrLen; skip++) 526 data.get(); 527 528 StackTraceElement[] steArray = new StackTraceElement[stackDepth]; 529 530 /* 531 * Pull out the stack trace. 532 */ 533 for (int sti = 0; sti < stackDepth; sti++) { 534 int methodClassNameIndex, methodNameIndex; 535 int methodSourceFileIndex; 536 short lineNumber; 537 String methodClassName, methodName, methodSourceFile; 538 539 methodClassNameIndex = (data.getShort() & 0xffff); 540 methodNameIndex = (data.getShort() & 0xffff); 541 methodSourceFileIndex = (data.getShort() & 0xffff); 542 lineNumber = data.getShort(); 543 544 methodClassName = classNames[methodClassNameIndex]; 545 methodName = methodNames[methodNameIndex]; 546 methodSourceFile = fileNames[methodSourceFileIndex]; 547 548 steArray[sti] = new StackTraceElement(methodClassName, 549 methodName, methodSourceFile, lineNumber); 550 551 /* we've consumed 8 bytes; gobble up any extra */ 552 for (int skip = 9; skip < stackFrameLen; skip++) 553 data.get(); 554 } 555 556 list.add(new AllocationInfo(classNames[classNameIndex], 557 totalSize, (short) threadId, steArray)); 558 } 559 560 // sort biggest allocations first. 561 Collections.sort(list); 562 563 client.getClientData().setAllocations(list.toArray(new AllocationInfo[numEntries])); 564 client.update(Client.CHANGE_HEAP_ALLOCATIONS); 565 } 566 567 /* 568 * For debugging: dump the contents of an AllocRecord array. 569 * 570 * The array starts with the oldest known allocation and ends with 571 * the most recent allocation. 572 */ 573 @SuppressWarnings("unused") dumpRecords(AllocationInfo[] records)574 private static void dumpRecords(AllocationInfo[] records) { 575 System.out.println("Found " + records.length + " records:"); 576 577 for (AllocationInfo rec: records) { 578 System.out.println("tid=" + rec.getThreadId() + " " 579 + rec.getAllocatedClass() + " (" + rec.getSize() + " bytes)"); 580 581 for (StackTraceElement ste: rec.getStackTrace()) { 582 if (ste.isNativeMethod()) { 583 System.out.println(" " + ste.getClassName() 584 + "." + ste.getMethodName() 585 + " (Native method)"); 586 } else { 587 System.out.println(" " + ste.getClassName() 588 + "." + ste.getMethodName() 589 + " (" + ste.getFileName() 590 + ":" + ste.getLineNumber() + ")"); 591 } 592 } 593 } 594 } 595 596 } 597 598