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