• 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 
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