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