2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.ddmlib;
19 import com.android.ddmlib.ClientData.AllocationTrackingStatus;
20 import com.android.ddmlib.ClientData.IHprofDumpHandler;
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;
29 * Handle heap status updates.
31 final class HandleHeap extends ChunkHandler {
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");
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
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;
56 private static final HandleHeap mInst = new HandleHeap();
58 private HandleHeap() {}
61 * Register for the packets we expect to get from the client.
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);
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);
88 public void clientDisconnected(Client client) {}
91 * Chunk handler entry point.
94 public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
95 Log.d("ddm-heap", "handling " + ChunkHandler.name(type));
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);
114 handleUnknownChunk(client, type, data, isReply, msgId);
119 * Handle a heap info message.
121 private void handleHPIF(Client client, ByteBuffer data) {
122 Log.d("ddm-heap", "HPIF!");
124 int numHeaps = data.getInt();
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;
137 client.getClientData().setHeapInfo(heapId, maxHeapSize,
138 heapSize, bytesAllocated, objectsAllocated);
139 client.update(Client.CHANGE_HEAP_DATA);
141 } catch (BufferUnderflowException ex) {
142 Log.w("ddm-heap", "malformed HPIF chunk from client");
147 * Send an HPIF (HeaP InFo) request to the client.
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);
156 finishChunkPacket(packet, CHUNK_HPIF, buf.position());
157 Log.d("ddm-heap", "Sending " + name(CHUNK_HPIF) + ": when=" + when);
158 client.sendAndConsume(packet, mInst);
162 * Handle a heap segment series start message.
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.
168 //xxx todo: only clear data that belongs to the heap mentioned in <data>.
169 client.getClientData().getVmHeapData().clearHeapData();
173 * Handle a heap segment series end message.
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.
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);
185 * Handle a heap segment message.
187 private void handleHPSG(Client client, ByteBuffer data) {
188 byte dataCopy[] = new byte[data.limit()];
191 data = ByteBuffer.wrap(dataCopy);
192 client.getClientData().getVmHeapData().addHeapData(data);
193 //xxx todo: add to the heap mentioned in <data>
197 * Sends an HPSG (HeaP SeGment) request to the client.
199 public static void sendHPSG(Client client, int when, int what)
202 ByteBuffer rawBuf = allocBuffer(2);
203 JdwpPacket packet = new JdwpPacket(rawBuf);
204 ByteBuffer buf = getChunkDataBuf(rawBuf);
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);
216 * Sends an HPGC request to the client.
218 public static void sendHPGC(Client client)
220 ByteBuffer rawBuf = allocBuffer(0);
221 JdwpPacket packet = new JdwpPacket(rawBuf);
222 ByteBuffer buf = getChunkDataBuf(rawBuf);
226 finishChunkPacket(packet, CHUNK_HPGC, buf.position());
227 Log.d("ddm-heap", "Sending " + name(CHUNK_HPGC));
228 client.sendAndConsume(packet, mInst);
232 * Sends an HPDU request to the client.
234 * We will get an HPDU response when the heap dump has completed. On
235 * failure we get a generic failure response.
237 * @param fileName name of output file (on device)
239 public static void sendHPDU(Client client, String fileName)
241 ByteBuffer rawBuf = allocBuffer(4 + fileName.length() * 2);
242 JdwpPacket packet = new JdwpPacket(rawBuf);
243 ByteBuffer buf = getChunkDataBuf(rawBuf);
245 buf.putInt(fileName.length());
246 putString(buf, fileName);
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);
255 * Sends an HPDS request to the client.
257 * We will get an HPDS response when the heap dump has completed. On
258 * failure we get a generic failure response.
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.
264 * @param fileName name of output file (on device)
266 public static void sendHPDS(Client client)
268 ByteBuffer rawBuf = allocBuffer(0);
269 JdwpPacket packet = new JdwpPacket(rawBuf);
270 ByteBuffer buf = getChunkDataBuf(rawBuf);
272 finishChunkPacket(packet, CHUNK_HPDS, buf.position());
273 Log.d("ddm-heap", "Sending " + name(CHUNK_HPDS));
274 client.sendAndConsume(packet, mInst);
278 * Handle notification of completion of a HeaP DUmp.
280 private void handleHPDU(Client client, ByteBuffer data) {
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);
287 // get the dump result
290 // get the app-level handler for HPROF dump
291 IHprofDumpHandler handler = ClientData.getHprofDumpHandler();
292 if (handler != null) {
294 handler.onSuccess(filename, client);
296 Log.d("ddm-heap", "Heap dump request has finished");
298 handler.onEndFailure(client, null);
299 Log.w("ddm-heap", "Heap dump request failed (check device log)");
305 * Handle HeaP Dump Streaming response. "data" contains the full
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);
314 Log.d("ddm-hprof", "got hprof file, size: " + data.capacity() + " bytes");
316 handler.onSuccess(stuff, client);
321 * Sends a REAE (REcent Allocation Enable) request to the client.
323 public static void sendREAE(Client client, boolean enable)
325 ByteBuffer rawBuf = allocBuffer(1);
326 JdwpPacket packet = new JdwpPacket(rawBuf);
327 ByteBuffer buf = getChunkDataBuf(rawBuf);
329 buf.put((byte) (enable ? 1 : 0));
331 finishChunkPacket(packet, CHUNK_REAE, buf.position());
332 Log.d("ddm-heap", "Sending " + name(CHUNK_REAE) + ": " + enable);
333 client.sendAndConsume(packet, mInst);
337 * Sends a REAQ (REcent Allocation Query) request to the client.
339 public static void sendREAQ(Client client)
341 ByteBuffer rawBuf = allocBuffer(0);
342 JdwpPacket packet = new JdwpPacket(rawBuf);
343 ByteBuffer buf = getChunkDataBuf(rawBuf);
347 finishChunkPacket(packet, CHUNK_REAQ, buf.position());
348 Log.d("ddm-heap", "Sending " + name(CHUNK_REAQ));
349 client.sendAndConsume(packet, mInst);
353 * Sends a REAL (REcent ALlocation) request to the client.
355 public static void sendREAL(Client client)
357 ByteBuffer rawBuf = allocBuffer(0);
358 JdwpPacket packet = new JdwpPacket(rawBuf);
359 ByteBuffer buf = getChunkDataBuf(rawBuf);
363 finishChunkPacket(packet, CHUNK_REAL, buf.position());
364 Log.d("ddm-heap", "Sending " + name(CHUNK_REAL));
365 client.sendAndConsume(packet, mInst);
369 * Handle the response from our REcent Allocation Query message.
371 private void handleREAQ(Client client, ByteBuffer data) {
374 enabled = (data.get() != 0);
375 Log.d("ddm-heap", "REAQ says: enabled=" + enabled);
377 client.getClientData().setAllocationStatus(enabled ?
378 AllocationTrackingStatus.ON : AllocationTrackingStatus.OFF);
379 client.update(Client.CHANGE_HEAP_ALLOCATION_STATUS);
383 * Converts a VM class descriptor string ("Landroid/os/Debug;") to
384 * a dot-notation class name ("android.os.Debug").
386 private String descriptorToDot(String str) {
387 // count the number of arrays.
389 while (str.startsWith("[")) {
390 str = str.substring(1);
394 int len = str.length();
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('/', '.');
401 // convert the basic types
402 if ("C".equals(str)) {
404 } else if ("B".equals(str)) {
406 } else if ("Z".equals(str)) {
408 } else if ("S".equals(str)) {
410 } else if ("I".equals(str)) {
412 } else if ("J".equals(str)) {
414 } else if ("F".equals(str)) {
416 } else if ("D".equals(str)) {
421 // now add the array part
422 for (int a = 0 ; a < array; a++) {
430 * Reads a string table out of "data".
432 * This is just a serial collection of strings, each of which is a
433 * four-byte length followed by UTF-16 data.
435 private void readStringTable(ByteBuffer data, String[] strings) {
436 int count = strings.length;
439 for (i = 0; i < count; i++) {
440 int nameLen = data.getInt();
441 String descriptor = getString(data, nameLen);
442 strings[i] = descriptorToDot(descriptor);
447 * Handle a REcent ALlocation response.
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
459 * (4b) total allocation size
461 * (2b) allocated object's class name index
463 * For each stack frame:
464 * (2b) method's class 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
472 * As with other DDM traffic, strings are sent as a 4-byte length
473 * followed by UTF-16 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;
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);
495 * Skip forward to the strings and read them.
497 data.position(offsetToStrings);
499 String[] classNames = new String[numClassNames];
500 String[] methodNames = new String[numMethodNames];
501 String[] fileNames = new String[numFileNames];
503 readStringTable(data, classNames);
504 readStringTable(data, methodNames);
505 //System.out.println("METHODS: "
506 // + java.util.Arrays.deepToString(methodNames));
507 readStringTable(data, fileNames);
510 * Skip back to a point just past the header and start reading
513 data.position(messageHdrLen);
515 ArrayList<AllocationInfo> list = new ArrayList<AllocationInfo>(numEntries);
516 for (int i = 0; i < numEntries; i++) {
518 int threadId, classNameIndex, stackDepth;
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++)
528 StackTraceElement[] steArray = new StackTraceElement[stackDepth];
531 * Pull out the stack trace.
533 for (int sti = 0; sti < stackDepth; sti++) {
534 int methodClassNameIndex, methodNameIndex;
535 int methodSourceFileIndex;
537 String methodClassName, methodName, methodSourceFile;
539 methodClassNameIndex = (data.getShort() & 0xffff);
540 methodNameIndex = (data.getShort() & 0xffff);
541 methodSourceFileIndex = (data.getShort() & 0xffff);
542 lineNumber = data.getShort();
544 methodClassName = classNames[methodClassNameIndex];
545 methodName = methodNames[methodNameIndex];
546 methodSourceFile = fileNames[methodSourceFileIndex];
548 steArray[sti] = new StackTraceElement(methodClassName,
549 methodName, methodSourceFile, lineNumber);
551 /* we've consumed 8 bytes; gobble up any extra */
552 for (int skip = 9; skip < stackFrameLen; skip++)
556 list.add(new AllocationInfo(classNames[classNameIndex],
557 totalSize, (short) threadId, steArray));
560 // sort biggest allocations first.
561 Collections.sort(list);
563 client.getClientData().setAllocations(list.toArray(new AllocationInfo[numEntries]));
564 client.update(Client.CHANGE_HEAP_ALLOCATIONS);
568 * For debugging: dump the contents of an AllocRecord array.
570 * The array starts with the oldest known allocation and ends with
571 * the most recent allocation.
573 @SuppressWarnings("unused")
574 private static void dumpRecords(AllocationInfo[] records) {
575 System.out.println("Found " + records.length + " records:");
577 for (AllocationInfo rec: records) {
578 System.out.println("tid=" + rec.getThreadId() + " "
579 + rec.getAllocatedClass() + " (" + rec.getSize() + " bytes)");
581 for (StackTraceElement ste: rec.getStackTrace()) {
582 if (ste.isNativeMethod()) {
583 System.out.println(" " + ste.getClassName()
584 + "." + ste.getMethodName()
585 + " (Native method)");
587 System.out.println(" " + ste.getClassName()
588 + "." + ste.getMethodName()
589 + " (" + ste.getFileName()
590 + ":" + ste.getLineNumber() + ")");