• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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