| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ddmlib; |
| |
| import com.android.ddmlib.ClientData.AllocationTrackingStatus; |
| import com.android.ddmlib.ClientData.IHprofDumpHandler; |
| |
| import java.io.IOException; |
| import java.nio.BufferUnderflowException; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| |
| /** |
| * Handle heap status updates. |
| */ |
| final class HandleHeap extends ChunkHandler { |
| |
| public static final int CHUNK_HPIF = type("HPIF"); |
| public static final int CHUNK_HPST = type("HPST"); |
| public static final int CHUNK_HPEN = type("HPEN"); |
| public static final int CHUNK_HPSG = type("HPSG"); |
| public static final int CHUNK_HPGC = type("HPGC"); |
| public static final int CHUNK_HPDU = type("HPDU"); |
| public static final int CHUNK_HPDS = type("HPDS"); |
| public static final int CHUNK_REAE = type("REAE"); |
| public static final int CHUNK_REAQ = type("REAQ"); |
| public static final int CHUNK_REAL = type("REAL"); |
| |
| // args to sendHPSG |
| public static final int WHEN_DISABLE = 0; |
| public static final int WHEN_GC = 1; |
| public static final int WHAT_MERGE = 0; // merge adjacent objects |
| public static final int WHAT_OBJ = 1; // keep objects distinct |
| |
| // args to sendHPIF |
| public static final int HPIF_WHEN_NEVER = 0; |
| public static final int HPIF_WHEN_NOW = 1; |
| public static final int HPIF_WHEN_NEXT_GC = 2; |
| public static final int HPIF_WHEN_EVERY_GC = 3; |
| |
| private static final HandleHeap mInst = new HandleHeap(); |
| |
| private HandleHeap() {} |
| |
| /** |
| * Register for the packets we expect to get from the client. |
| */ |
| public static void register(MonitorThread mt) { |
| mt.registerChunkHandler(CHUNK_HPIF, mInst); |
| mt.registerChunkHandler(CHUNK_HPST, mInst); |
| mt.registerChunkHandler(CHUNK_HPEN, mInst); |
| mt.registerChunkHandler(CHUNK_HPSG, mInst); |
| mt.registerChunkHandler(CHUNK_HPDS, mInst); |
| mt.registerChunkHandler(CHUNK_REAQ, mInst); |
| mt.registerChunkHandler(CHUNK_REAL, mInst); |
| } |
| |
| /** |
| * Client is ready. |
| */ |
| @Override |
| public void clientReady(Client client) throws IOException { |
| if (client.isHeapUpdateEnabled()) { |
| //sendHPSG(client, WHEN_GC, WHAT_MERGE); |
| sendHPIF(client, HPIF_WHEN_EVERY_GC); |
| } |
| } |
| |
| /** |
| * Client went away. |
| */ |
| @Override |
| public void clientDisconnected(Client client) {} |
| |
| /** |
| * Chunk handler entry point. |
| */ |
| @Override |
| public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) { |
| Log.d("ddm-heap", "handling " + ChunkHandler.name(type)); |
| |
| if (type == CHUNK_HPIF) { |
| handleHPIF(client, data); |
| } else if (type == CHUNK_HPST) { |
| handleHPST(client, data); |
| } else if (type == CHUNK_HPEN) { |
| handleHPEN(client, data); |
| } else if (type == CHUNK_HPSG) { |
| handleHPSG(client, data); |
| } else if (type == CHUNK_HPDU) { |
| handleHPDU(client, data); |
| } else if (type == CHUNK_HPDS) { |
| handleHPDS(client, data); |
| } else if (type == CHUNK_REAQ) { |
| handleREAQ(client, data); |
| } else if (type == CHUNK_REAL) { |
| handleREAL(client, data); |
| } else { |
| handleUnknownChunk(client, type, data, isReply, msgId); |
| } |
| } |
| |
| /* |
| * Handle a heap info message. |
| */ |
| private void handleHPIF(Client client, ByteBuffer data) { |
| Log.d("ddm-heap", "HPIF!"); |
| try { |
| int numHeaps = data.getInt(); |
| |
| for (int i = 0; i < numHeaps; i++) { |
| int heapId = data.getInt(); |
| @SuppressWarnings("unused") |
| long timeStamp = data.getLong(); |
| @SuppressWarnings("unused") |
| byte reason = data.get(); |
| long maxHeapSize = (long)data.getInt() & 0x00ffffffff; |
| long heapSize = (long)data.getInt() & 0x00ffffffff; |
| long bytesAllocated = (long)data.getInt() & 0x00ffffffff; |
| long objectsAllocated = (long)data.getInt() & 0x00ffffffff; |
| |
| client.getClientData().setHeapInfo(heapId, maxHeapSize, |
| heapSize, bytesAllocated, objectsAllocated); |
| client.update(Client.CHANGE_HEAP_DATA); |
| } |
| } catch (BufferUnderflowException ex) { |
| Log.w("ddm-heap", "malformed HPIF chunk from client"); |
| } |
| } |
| |
| /** |
| * Send an HPIF (HeaP InFo) request to the client. |
| */ |
| public static void sendHPIF(Client client, int when) throws IOException { |
| ByteBuffer rawBuf = allocBuffer(1); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| buf.put((byte)when); |
| |
| finishChunkPacket(packet, CHUNK_HPIF, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_HPIF) + ": when=" + when); |
| client.sendAndConsume(packet, mInst); |
| } |
| |
| /* |
| * Handle a heap segment series start message. |
| */ |
| private void handleHPST(Client client, ByteBuffer data) { |
| /* Clear out any data that's sitting around to |
| * get ready for the chunks that are about to come. |
| */ |
| //xxx todo: only clear data that belongs to the heap mentioned in <data>. |
| client.getClientData().getVmHeapData().clearHeapData(); |
| } |
| |
| /* |
| * Handle a heap segment series end message. |
| */ |
| private void handleHPEN(Client client, ByteBuffer data) { |
| /* Let the UI know that we've received all of the |
| * data for this heap. |
| */ |
| //xxx todo: only seal data that belongs to the heap mentioned in <data>. |
| client.getClientData().getVmHeapData().sealHeapData(); |
| client.update(Client.CHANGE_HEAP_DATA); |
| } |
| |
| /* |
| * Handle a heap segment message. |
| */ |
| private void handleHPSG(Client client, ByteBuffer data) { |
| byte dataCopy[] = new byte[data.limit()]; |
| data.rewind(); |
| data.get(dataCopy); |
| data = ByteBuffer.wrap(dataCopy); |
| client.getClientData().getVmHeapData().addHeapData(data); |
| //xxx todo: add to the heap mentioned in <data> |
| } |
| |
| /** |
| * Sends an HPSG (HeaP SeGment) request to the client. |
| */ |
| public static void sendHPSG(Client client, int when, int what) |
| throws IOException { |
| |
| ByteBuffer rawBuf = allocBuffer(2); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| buf.put((byte)when); |
| buf.put((byte)what); |
| |
| finishChunkPacket(packet, CHUNK_HPSG, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_HPSG) + ": when=" |
| + when + ", what=" + what); |
| client.sendAndConsume(packet, mInst); |
| } |
| |
| /** |
| * Sends an HPGC request to the client. |
| */ |
| public static void sendHPGC(Client client) |
| throws IOException { |
| ByteBuffer rawBuf = allocBuffer(0); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| // no data |
| |
| finishChunkPacket(packet, CHUNK_HPGC, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_HPGC)); |
| client.sendAndConsume(packet, mInst); |
| } |
| |
| /** |
| * Sends an HPDU request to the client. |
| * |
| * We will get an HPDU response when the heap dump has completed. On |
| * failure we get a generic failure response. |
| * |
| * @param fileName name of output file (on device) |
| */ |
| public static void sendHPDU(Client client, String fileName) |
| throws IOException { |
| ByteBuffer rawBuf = allocBuffer(4 + fileName.length() * 2); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| buf.putInt(fileName.length()); |
| putString(buf, fileName); |
| |
| finishChunkPacket(packet, CHUNK_HPDU, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_HPDU) + " '" + fileName +"'"); |
| client.sendAndConsume(packet, mInst); |
| client.getClientData().setPendingHprofDump(fileName); |
| } |
| |
| /** |
| * Sends an HPDS request to the client. |
| * |
| * We will get an HPDS response when the heap dump has completed. On |
| * failure we get a generic failure response. |
| * |
| * This is more expensive for the device than HPDU, because the entire |
| * heap dump is held in RAM instead of spooled out to a temp file. On |
| * the other hand, permission to write to /sdcard is not required. |
| * |
| * @param fileName name of output file (on device) |
| */ |
| public static void sendHPDS(Client client) |
| throws IOException { |
| ByteBuffer rawBuf = allocBuffer(0); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| finishChunkPacket(packet, CHUNK_HPDS, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_HPDS)); |
| client.sendAndConsume(packet, mInst); |
| } |
| |
| /* |
| * Handle notification of completion of a HeaP DUmp. |
| */ |
| private void handleHPDU(Client client, ByteBuffer data) { |
| byte result; |
| |
| // get the filename and make the client not have pending HPROF dump anymore. |
| String filename = client.getClientData().getPendingHprofDump(); |
| client.getClientData().setPendingHprofDump(null); |
| |
| // get the dump result |
| result = data.get(); |
| |
| // get the app-level handler for HPROF dump |
| IHprofDumpHandler handler = ClientData.getHprofDumpHandler(); |
| if (handler != null) { |
| if (result == 0) { |
| handler.onSuccess(filename, client); |
| |
| Log.d("ddm-heap", "Heap dump request has finished"); |
| } else { |
| handler.onEndFailure(client, null); |
| Log.w("ddm-heap", "Heap dump request failed (check device log)"); |
| } |
| } |
| } |
| |
| /* |
| * Handle HeaP Dump Streaming response. "data" contains the full |
| * hprof dump. |
| */ |
| private void handleHPDS(Client client, ByteBuffer data) { |
| IHprofDumpHandler handler = ClientData.getHprofDumpHandler(); |
| if (handler != null) { |
| byte[] stuff = new byte[data.capacity()]; |
| data.get(stuff, 0, stuff.length); |
| |
| Log.d("ddm-hprof", "got hprof file, size: " + data.capacity() + " bytes"); |
| |
| handler.onSuccess(stuff, client); |
| } |
| } |
| |
| /** |
| * Sends a REAE (REcent Allocation Enable) request to the client. |
| */ |
| public static void sendREAE(Client client, boolean enable) |
| throws IOException { |
| ByteBuffer rawBuf = allocBuffer(1); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| buf.put((byte) (enable ? 1 : 0)); |
| |
| finishChunkPacket(packet, CHUNK_REAE, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_REAE) + ": " + enable); |
| client.sendAndConsume(packet, mInst); |
| } |
| |
| /** |
| * Sends a REAQ (REcent Allocation Query) request to the client. |
| */ |
| public static void sendREAQ(Client client) |
| throws IOException { |
| ByteBuffer rawBuf = allocBuffer(0); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| // no data |
| |
| finishChunkPacket(packet, CHUNK_REAQ, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_REAQ)); |
| client.sendAndConsume(packet, mInst); |
| } |
| |
| /** |
| * Sends a REAL (REcent ALlocation) request to the client. |
| */ |
| public static void sendREAL(Client client) |
| throws IOException { |
| ByteBuffer rawBuf = allocBuffer(0); |
| JdwpPacket packet = new JdwpPacket(rawBuf); |
| ByteBuffer buf = getChunkDataBuf(rawBuf); |
| |
| // no data |
| |
| finishChunkPacket(packet, CHUNK_REAL, buf.position()); |
| Log.d("ddm-heap", "Sending " + name(CHUNK_REAL)); |
| client.sendAndConsume(packet, mInst); |
| } |
| |
| /* |
| * Handle the response from our REcent Allocation Query message. |
| */ |
| private void handleREAQ(Client client, ByteBuffer data) { |
| boolean enabled; |
| |
| enabled = (data.get() != 0); |
| Log.d("ddm-heap", "REAQ says: enabled=" + enabled); |
| |
| client.getClientData().setAllocationStatus(enabled ? |
| AllocationTrackingStatus.ON : AllocationTrackingStatus.OFF); |
| client.update(Client.CHANGE_HEAP_ALLOCATION_STATUS); |
| } |
| |
| /** |
| * Converts a VM class descriptor string ("Landroid/os/Debug;") to |
| * a dot-notation class name ("android.os.Debug"). |
| */ |
| private String descriptorToDot(String str) { |
| // count the number of arrays. |
| int array = 0; |
| while (str.startsWith("[")) { |
| str = str.substring(1); |
| array++; |
| } |
| |
| int len = str.length(); |
| |
| /* strip off leading 'L' and trailing ';' if appropriate */ |
| if (len >= 2 && str.charAt(0) == 'L' && str.charAt(len - 1) == ';') { |
| str = str.substring(1, len-1); |
| str = str.replace('/', '.'); |
| } else { |
| // convert the basic types |
| if ("C".equals(str)) { |
| str = "char"; |
| } else if ("B".equals(str)) { |
| str = "byte"; |
| } else if ("Z".equals(str)) { |
| str = "boolean"; |
| } else if ("S".equals(str)) { |
| str = "short"; |
| } else if ("I".equals(str)) { |
| str = "int"; |
| } else if ("J".equals(str)) { |
| str = "long"; |
| } else if ("F".equals(str)) { |
| str = "float"; |
| } else if ("D".equals(str)) { |
| str = "double"; |
| } |
| } |
| |
| // now add the array part |
| for (int a = 0 ; a < array; a++) { |
| str = str + "[]"; |
| } |
| |
| return str; |
| } |
| |
| /** |
| * Reads a string table out of "data". |
| * |
| * This is just a serial collection of strings, each of which is a |
| * four-byte length followed by UTF-16 data. |
| */ |
| private void readStringTable(ByteBuffer data, String[] strings) { |
| int count = strings.length; |
| int i; |
| |
| for (i = 0; i < count; i++) { |
| int nameLen = data.getInt(); |
| String descriptor = getString(data, nameLen); |
| strings[i] = descriptorToDot(descriptor); |
| } |
| } |
| |
| /* |
| * Handle a REcent ALlocation response. |
| * |
| * Message header (all values big-endian): |
| * (1b) message header len (to allow future expansion); includes itself |
| * (1b) entry header len |
| * (1b) stack frame len |
| * (2b) number of entries |
| * (4b) offset to string table from start of message |
| * (2b) number of class name strings |
| * (2b) number of method name strings |
| * (2b) number of source file name strings |
| * For each entry: |
| * (4b) total allocation size |
| * (2b) threadId |
| * (2b) allocated object's class name index |
| * (1b) stack depth |
| * For each stack frame: |
| * (2b) method's class name |
| * (2b) method name |
| * (2b) method source file |
| * (2b) line number, clipped to 32767; -2 if native; -1 if no source |
| * (xb) class name strings |
| * (xb) method name strings |
| * (xb) source file strings |
| * |
| * As with other DDM traffic, strings are sent as a 4-byte length |
| * followed by UTF-16 data. |
| */ |
| private void handleREAL(Client client, ByteBuffer data) { |
| Log.e("ddm-heap", "*** Received " + name(CHUNK_REAL)); |
| int messageHdrLen, entryHdrLen, stackFrameLen; |
| int numEntries, offsetToStrings; |
| int numClassNames, numMethodNames, numFileNames; |
| |
| /* |
| * Read the header. |
| */ |
| messageHdrLen = (data.get() & 0xff); |
| entryHdrLen = (data.get() & 0xff); |
| stackFrameLen = (data.get() & 0xff); |
| numEntries = (data.getShort() & 0xffff); |
| offsetToStrings = data.getInt(); |
| numClassNames = (data.getShort() & 0xffff); |
| numMethodNames = (data.getShort() & 0xffff); |
| numFileNames = (data.getShort() & 0xffff); |
| |
| |
| /* |
| * Skip forward to the strings and read them. |
| */ |
| data.position(offsetToStrings); |
| |
| String[] classNames = new String[numClassNames]; |
| String[] methodNames = new String[numMethodNames]; |
| String[] fileNames = new String[numFileNames]; |
| |
| readStringTable(data, classNames); |
| readStringTable(data, methodNames); |
| //System.out.println("METHODS: " |
| // + java.util.Arrays.deepToString(methodNames)); |
| readStringTable(data, fileNames); |
| |
| /* |
| * Skip back to a point just past the header and start reading |
| * entries. |
| */ |
| data.position(messageHdrLen); |
| |
| ArrayList<AllocationInfo> list = new ArrayList<AllocationInfo>(numEntries); |
| int allocNumber = numEntries; // order value for the entry. This is sent in reverse order. |
| for (int i = 0; i < numEntries; i++) { |
| int totalSize; |
| int threadId, classNameIndex, stackDepth; |
| |
| totalSize = data.getInt(); |
| threadId = (data.getShort() & 0xffff); |
| classNameIndex = (data.getShort() & 0xffff); |
| stackDepth = (data.get() & 0xff); |
| /* we've consumed 9 bytes; gobble up any extra */ |
| for (int skip = 9; skip < entryHdrLen; skip++) |
| data.get(); |
| |
| StackTraceElement[] steArray = new StackTraceElement[stackDepth]; |
| |
| /* |
| * Pull out the stack trace. |
| */ |
| for (int sti = 0; sti < stackDepth; sti++) { |
| int methodClassNameIndex, methodNameIndex; |
| int methodSourceFileIndex; |
| short lineNumber; |
| String methodClassName, methodName, methodSourceFile; |
| |
| methodClassNameIndex = (data.getShort() & 0xffff); |
| methodNameIndex = (data.getShort() & 0xffff); |
| methodSourceFileIndex = (data.getShort() & 0xffff); |
| lineNumber = data.getShort(); |
| |
| methodClassName = classNames[methodClassNameIndex]; |
| methodName = methodNames[methodNameIndex]; |
| methodSourceFile = fileNames[methodSourceFileIndex]; |
| |
| steArray[sti] = new StackTraceElement(methodClassName, |
| methodName, methodSourceFile, lineNumber); |
| |
| /* we've consumed 8 bytes; gobble up any extra */ |
| for (int skip = 9; skip < stackFrameLen; skip++) |
| data.get(); |
| } |
| |
| list.add(new AllocationInfo(allocNumber--, classNames[classNameIndex], |
| totalSize, (short) threadId, steArray)); |
| } |
| |
| client.getClientData().setAllocations(list.toArray(new AllocationInfo[numEntries])); |
| client.update(Client.CHANGE_HEAP_ALLOCATIONS); |
| } |
| |
| /* |
| * For debugging: dump the contents of an AllocRecord array. |
| * |
| * The array starts with the oldest known allocation and ends with |
| * the most recent allocation. |
| */ |
| @SuppressWarnings("unused") |
| private static void dumpRecords(AllocationInfo[] records) { |
| System.out.println("Found " + records.length + " records:"); |
| |
| for (AllocationInfo rec: records) { |
| System.out.println("tid=" + rec.getThreadId() + " " |
| + rec.getAllocatedClass() + " (" + rec.getSize() + " bytes)"); |
| |
| for (StackTraceElement ste: rec.getStackTrace()) { |
| if (ste.isNativeMethod()) { |
| System.out.println(" " + ste.getClassName() |
| + "." + ste.getMethodName() |
| + " (Native method)"); |
| } else { |
| System.out.println(" " + ste.getClassName() |
| + "." + ste.getMethodName() |
| + " (" + ste.getFileName() |
| + ":" + ste.getLineNumber() + ")"); |
| } |
| } |
| } |
| } |
| |
| } |
| |