• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.ddmuilib.heap;
18 
19 import com.android.ddmlib.Client;
20 import com.android.ddmlib.Log;
21 import com.android.ddmlib.NativeAllocationInfo;
22 import com.android.ddmlib.NativeLibraryMapInfo;
23 import com.android.ddmlib.NativeStackCallInfo;
24 import com.android.ddmuilib.Addr2Line;
25 import com.android.ddmuilib.BaseHeapPanel;
26 import com.android.ddmuilib.ITableFocusListener;
27 import com.android.ddmuilib.ITableFocusListener.IFocusedTableActivator;
28 import com.android.ddmuilib.ImageLoader;
29 import com.android.ddmuilib.TableHelper;
30 
31 import org.eclipse.jface.dialogs.MessageDialog;
32 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
33 import org.eclipse.jface.preference.IPreferenceStore;
34 import org.eclipse.jface.viewers.TreeViewer;
35 import org.eclipse.swt.SWT;
36 import org.eclipse.swt.dnd.Clipboard;
37 import org.eclipse.swt.dnd.TextTransfer;
38 import org.eclipse.swt.dnd.Transfer;
39 import org.eclipse.swt.events.FocusEvent;
40 import org.eclipse.swt.events.FocusListener;
41 import org.eclipse.swt.events.ModifyEvent;
42 import org.eclipse.swt.events.ModifyListener;
43 import org.eclipse.swt.events.SelectionAdapter;
44 import org.eclipse.swt.events.SelectionEvent;
45 import org.eclipse.swt.graphics.Rectangle;
46 import org.eclipse.swt.layout.FormAttachment;
47 import org.eclipse.swt.layout.FormData;
48 import org.eclipse.swt.layout.FormLayout;
49 import org.eclipse.swt.layout.GridData;
50 import org.eclipse.swt.layout.GridLayout;
51 import org.eclipse.swt.widgets.Button;
52 import org.eclipse.swt.widgets.Combo;
53 import org.eclipse.swt.widgets.Composite;
54 import org.eclipse.swt.widgets.Control;
55 import org.eclipse.swt.widgets.Display;
56 import org.eclipse.swt.widgets.Event;
57 import org.eclipse.swt.widgets.FileDialog;
58 import org.eclipse.swt.widgets.Label;
59 import org.eclipse.swt.widgets.Listener;
60 import org.eclipse.swt.widgets.Sash;
61 import org.eclipse.swt.widgets.Shell;
62 import org.eclipse.swt.widgets.Text;
63 import org.eclipse.swt.widgets.ToolBar;
64 import org.eclipse.swt.widgets.ToolItem;
65 import org.eclipse.swt.widgets.Tree;
66 import org.eclipse.swt.widgets.TreeItem;
67 
68 import java.io.BufferedWriter;
69 import java.io.File;
70 import java.io.FileNotFoundException;
71 import java.io.FileReader;
72 import java.io.FileWriter;
73 import java.io.IOException;
74 import java.io.PrintWriter;
75 import java.io.Reader;
76 import java.lang.reflect.InvocationTargetException;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Collection;
80 import java.util.HashMap;
81 import java.util.Iterator;
82 import java.util.List;
83 import java.util.Map;
84 import java.util.Set;
85 
86 /** Panel to display native heap information. */
87 public class NativeHeapPanel extends BaseHeapPanel {
88     private static final boolean USE_OLD_RESOLVER;
89     static {
90         String useOldResolver = System.getenv("ANDROID_DDMS_OLD_SYMRESOLVER");
91         if (useOldResolver != null && useOldResolver.equalsIgnoreCase("true")) {
92             USE_OLD_RESOLVER = true;
93         } else {
94             USE_OLD_RESOLVER = false;
95         }
96     }
97     private final int MAX_DISPLAYED_ERROR_ITEMS = 5;
98 
99     private static final String TOOLTIP_EXPORT_DATA = "Export Heap Data";
100     private static final String TOOLTIP_ZYGOTE_ALLOCATIONS = "Show Zygote Allocations";
101     private static final String TOOLTIP_DIFFS_ONLY = "Only show new allocations not present in previous snapshot";
102     private static final String TOOLTIP_GROUPBY = "Group allocations by library.";
103 
104     private static final String EXPORT_DATA_IMAGE = "save.png";
105     private static final String ZYGOTE_IMAGE = "zygote.png";
106     private static final String DIFFS_ONLY_IMAGE = "diff.png";
107     private static final String GROUPBY_IMAGE = "groupby.png";
108 
109     private static final String SNAPSHOT_HEAP_BUTTON_TEXT = "Snapshot Current Native Heap Usage";
110     private static final String LOAD_HEAP_DATA_BUTTON_TEXT = "Import Heap Data";
111     private static final String SYMBOL_SEARCH_PATH_LABEL_TEXT = "Symbol Search Path:";
112     private static final String SYMBOL_SEARCH_PATH_TEXT_MESSAGE =
113             "List of colon separated paths to search for symbol debug information. See tooltip for examples.";
114     private static final String SYMBOL_SEARCH_PATH_TOOLTIP_TEXT =
115             "Colon separated paths that contain unstripped libraries with debug symbols.\n"
116                     + "e.g.: <android-src>/out/target/product/generic/symbols/system/lib:/path/to/my/app/obj/local/armeabi";
117 
118     private static final String PREFS_SHOW_DIFFS_ONLY = "nativeheap.show.diffs.only";
119     private static final String PREFS_SHOW_ZYGOTE_ALLOCATIONS = "nativeheap.show.zygote";
120     private static final String PREFS_GROUP_BY_LIBRARY = "nativeheap.grouby.library";
121     private static final String PREFS_SYMBOL_SEARCH_PATH = "nativeheap.search.path";
122     private static final String PREFS_SASH_HEIGHT_PERCENT = "nativeheap.sash.percent";
123     private static final String PREFS_LAST_IMPORTED_HEAPPATH = "nativeheap.last.import.path";
124     private IPreferenceStore mPrefStore;
125 
126     private List<NativeHeapSnapshot> mNativeHeapSnapshots;
127 
128     // Maintain the differences between a snapshot and its predecessor.
129     // mDiffSnapshots[i] = mNativeHeapSnapshots[i] - mNativeHeapSnapshots[i-1]
130     // The zeroth entry is null since there is no predecessor.
131     // The list is filled lazily on demand.
132     private List<NativeHeapSnapshot> mDiffSnapshots;
133 
134     private Map<Integer, List<NativeHeapSnapshot>> mImportedSnapshotsPerPid;
135 
136     private Button mSnapshotHeapButton;
137     private Button mLoadHeapDataButton;
138     private Text mSymbolSearchPathText;
139     private Combo mSnapshotIndexCombo;
140     private Label mMemoryAllocatedText;
141 
142     private TreeViewer mDetailsTreeViewer;
143     private TreeViewer mStackTraceTreeViewer;
144     private NativeHeapProviderByAllocations mContentProviderByAllocations;
145     private NativeHeapProviderByLibrary mContentProviderByLibrary;
146     private NativeHeapLabelProvider mDetailsTreeLabelProvider;
147 
148     private ToolBar mDetailsToolBar;
149     private ToolItem mGroupByButton;
150     private ToolItem mDiffsOnlyButton;
151     private ToolItem mShowZygoteAllocationsButton;
152     private ToolItem mExportHeapDataButton;
153 
NativeHeapPanel(IPreferenceStore prefStore)154     public NativeHeapPanel(IPreferenceStore prefStore) {
155         mPrefStore = prefStore;
156         mPrefStore.setDefault(PREFS_SASH_HEIGHT_PERCENT, 75);
157         mPrefStore.setDefault(PREFS_SYMBOL_SEARCH_PATH, "");
158         mPrefStore.setDefault(PREFS_GROUP_BY_LIBRARY, false);
159         mPrefStore.setDefault(PREFS_SHOW_ZYGOTE_ALLOCATIONS, true);
160         mPrefStore.setDefault(PREFS_SHOW_DIFFS_ONLY, false);
161 
162         mNativeHeapSnapshots = new ArrayList<NativeHeapSnapshot>();
163         mDiffSnapshots = new ArrayList<NativeHeapSnapshot>();
164         mImportedSnapshotsPerPid = new HashMap<Integer, List<NativeHeapSnapshot>>();
165     }
166 
167     /** {@inheritDoc} */
168     @Override
clientChanged(final Client client, int changeMask)169     public void clientChanged(final Client client, int changeMask) {
170         if (client != getCurrentClient()) {
171             return;
172         }
173 
174         if ((changeMask & Client.CHANGE_NATIVE_HEAP_DATA) != Client.CHANGE_NATIVE_HEAP_DATA) {
175             return;
176         }
177 
178         List<NativeAllocationInfo> allocations = client.getClientData().getNativeAllocationList();
179         if (allocations.size() == 0) {
180             return;
181         }
182 
183         // We need to clone this list since getClientData().getNativeAllocationList() clobbers
184         // the list on future updates
185         final List<NativeAllocationInfo> nativeAllocations = shallowCloneList(allocations);
186 
187         addNativeHeapSnapshot(new NativeHeapSnapshot(nativeAllocations));
188         updateDisplay();
189 
190         // Attempt to resolve symbols in a separate thread.
191         // The UI should be refreshed once the symbols have been resolved.
192         if (USE_OLD_RESOLVER) {
193             Thread t = new Thread(new SymbolResolverTask(nativeAllocations,
194                     client.getClientData().getMappedNativeLibraries()));
195             t.setName("Address to Symbol Resolver");
196             t.start();
197         } else {
198             Display.getDefault().asyncExec(new Runnable() {
199                 @Override
200                 public void run() {
201                     resolveSymbols();
202                     mDetailsTreeViewer.refresh();
203                     mStackTraceTreeViewer.refresh();
204                 }
205 
206                 public void resolveSymbols() {
207                     Shell shell = Display.getDefault().getActiveShell();
208                     ProgressMonitorDialog d = new ProgressMonitorDialog(shell);
209 
210                     NativeSymbolResolverTask resolver = new NativeSymbolResolverTask(
211                             nativeAllocations,
212                             client.getClientData().getMappedNativeLibraries(),
213                             mSymbolSearchPathText.getText());
214 
215                     try {
216                         d.run(true, true, resolver);
217                     } catch (InvocationTargetException e) {
218                         MessageDialog.openError(shell,
219                                 "Error Resolving Symbols",
220                                 e.getCause().getMessage());
221                         return;
222                     } catch (InterruptedException e) {
223                         return;
224                     }
225 
226                     MessageDialog.openInformation(shell, "Symbol Resolution Status",
227                             getResolutionStatusMessage(resolver));
228                 }
229             });
230         }
231     }
232 
getResolutionStatusMessage(NativeSymbolResolverTask resolver)233     private String getResolutionStatusMessage(NativeSymbolResolverTask resolver) {
234         StringBuilder sb = new StringBuilder();
235         sb.append("Symbol Resolution Complete.\n\n");
236 
237         // show addresses that were not mapped
238         Set<Long> unmappedAddresses = resolver.getUnmappedAddresses();
239         if (unmappedAddresses.size() > 0) {
240             sb.append(String.format("Unmapped addresses (%d): ",
241                     unmappedAddresses.size()));
242             sb.append(getSampleForDisplay(unmappedAddresses));
243             sb.append('\n');
244         }
245 
246         // show libraries that were not present on disk
247         Set<String> notFoundLibraries = resolver.getNotFoundLibraries();
248         if (notFoundLibraries.size() > 0) {
249             sb.append(String.format("Libraries not found on disk (%d): ",
250                     notFoundLibraries.size()));
251             sb.append(getSampleForDisplay(notFoundLibraries));
252             sb.append('\n');
253         }
254 
255         // show addresses that were mapped but not resolved
256         Set<Long> unresolvableAddresses = resolver.getUnresolvableAddresses();
257         if (unresolvableAddresses.size() > 0) {
258             sb.append(String.format("Unresolved addresses (%d): ",
259                     unresolvableAddresses.size()));
260             sb.append(getSampleForDisplay(unresolvableAddresses));
261             sb.append('\n');
262         }
263 
264         if (resolver.getAddr2LineErrorMessage() != null) {
265             sb.append("Error launching addr2line: ");
266             sb.append(resolver.getAddr2LineErrorMessage());
267         }
268 
269         return sb.toString();
270     }
271 
272     /**
273      * Get the string representation for a collection of items.
274      * If there are more items than {@link #MAX_DISPLAYED_ERROR_ITEMS}, then only the first
275      * {@link #MAX_DISPLAYED_ERROR_ITEMS} items are taken into account,
276      * and an ellipsis is added at the end.
277      */
getSampleForDisplay(Collection<?> items)278     private String getSampleForDisplay(Collection<?> items) {
279         StringBuilder sb = new StringBuilder();
280 
281         int c = 1;
282         Iterator<?> it = items.iterator();
283         while (it.hasNext()) {
284             Object item = it.next();
285             if (item instanceof Long) {
286                 sb.append(String.format("0x%x", item));
287             } else {
288                 sb.append(item);
289             }
290 
291             if (c == MAX_DISPLAYED_ERROR_ITEMS && it.hasNext()) {
292                 sb.append(", ...");
293                 break;
294             } else if (it.hasNext()) {
295                 sb.append(", ");
296             }
297 
298             c++;
299         }
300         return sb.toString();
301     }
302 
addNativeHeapSnapshot(NativeHeapSnapshot snapshot)303     private void addNativeHeapSnapshot(NativeHeapSnapshot snapshot) {
304         mNativeHeapSnapshots.add(snapshot);
305 
306         // The diff snapshots are filled in lazily on demand.
307         // But the list needs to be the same size as mNativeHeapSnapshots, so we add a null.
308         mDiffSnapshots.add(null);
309     }
310 
shallowCloneList(List<NativeAllocationInfo> allocations)311     private List<NativeAllocationInfo> shallowCloneList(List<NativeAllocationInfo> allocations) {
312         List<NativeAllocationInfo> clonedList =
313                 new ArrayList<NativeAllocationInfo>(allocations.size());
314 
315         for (NativeAllocationInfo i : allocations) {
316             clonedList.add(i);
317         }
318 
319         return clonedList;
320     }
321 
322     @Override
deviceSelected()323     public void deviceSelected() {
324         // pass
325     }
326 
327     @Override
clientSelected()328     public void clientSelected() {
329         Client c = getCurrentClient();
330 
331         if (c == null) {
332             // if there is no client selected, then we disable the buttons but leave the
333             // display as is so that whatever snapshots are displayed continue to stay
334             // visible to the user.
335             mSnapshotHeapButton.setEnabled(false);
336             mLoadHeapDataButton.setEnabled(false);
337             return;
338         }
339 
340         mNativeHeapSnapshots = new ArrayList<NativeHeapSnapshot>();
341         mDiffSnapshots = new ArrayList<NativeHeapSnapshot>();
342 
343         mSnapshotHeapButton.setEnabled(true);
344         mLoadHeapDataButton.setEnabled(true);
345 
346         List<NativeHeapSnapshot> importedSnapshots = mImportedSnapshotsPerPid.get(
347                 c.getClientData().getPid());
348         if (importedSnapshots != null) {
349             for (NativeHeapSnapshot n : importedSnapshots) {
350                 addNativeHeapSnapshot(n);
351             }
352         }
353 
354         List<NativeAllocationInfo> allocations = c.getClientData().getNativeAllocationList();
355         allocations = shallowCloneList(allocations);
356 
357         if (allocations.size() > 0) {
358             addNativeHeapSnapshot(new NativeHeapSnapshot(allocations));
359         }
360 
361         updateDisplay();
362     }
363 
updateDisplay()364     private void updateDisplay() {
365         Display.getDefault().syncExec(new Runnable() {
366             @Override
367             public void run() {
368                 updateSnapshotIndexCombo();
369                 updateToolbars();
370 
371                 int lastSnapshotIndex = mNativeHeapSnapshots.size() - 1;
372                 displaySnapshot(lastSnapshotIndex);
373                 displayStackTraceForSelection();
374             }
375         });
376     }
377 
displaySelectedSnapshot()378     private void displaySelectedSnapshot() {
379         Display.getDefault().syncExec(new Runnable() {
380             @Override
381             public void run() {
382                 int idx = mSnapshotIndexCombo.getSelectionIndex();
383                 displaySnapshot(idx);
384             }
385         });
386     }
387 
displaySnapshot(int index)388     private void displaySnapshot(int index) {
389         if (index < 0 || mNativeHeapSnapshots.size() == 0) {
390             mDetailsTreeViewer.setInput(null);
391             mMemoryAllocatedText.setText("");
392             return;
393         }
394 
395         assert index < mNativeHeapSnapshots.size() : "Invalid snapshot index";
396 
397         NativeHeapSnapshot snapshot = mNativeHeapSnapshots.get(index);
398         if (mDiffsOnlyButton.getSelection() && index > 0) {
399             snapshot = getDiffSnapshot(index);
400         }
401 
402         mMemoryAllocatedText.setText(snapshot.getFormattedMemorySize());
403         mMemoryAllocatedText.pack();
404 
405         mDetailsTreeLabelProvider.setTotalSize(snapshot.getTotalSize());
406         mDetailsTreeViewer.setInput(snapshot);
407         mDetailsTreeViewer.refresh();
408     }
409 
410     /** Obtain the diff of snapshot[index] & snapshot[index-1] */
getDiffSnapshot(int index)411     private NativeHeapSnapshot getDiffSnapshot(int index) {
412         // if it was already computed, simply return that
413         NativeHeapSnapshot diffSnapshot = mDiffSnapshots.get(index);
414         if (diffSnapshot != null) {
415             return diffSnapshot;
416         }
417 
418         // compute the diff
419         NativeHeapSnapshot cur = mNativeHeapSnapshots.get(index);
420         NativeHeapSnapshot prev = mNativeHeapSnapshots.get(index - 1);
421         diffSnapshot = new NativeHeapDiffSnapshot(cur, prev);
422 
423         // cache for future use
424         mDiffSnapshots.set(index, diffSnapshot);
425 
426         return diffSnapshot;
427     }
428 
updateDisplayGrouping()429     private void updateDisplayGrouping() {
430         boolean groupByLibrary = mGroupByButton.getSelection();
431         mPrefStore.setValue(PREFS_GROUP_BY_LIBRARY, groupByLibrary);
432 
433         if (groupByLibrary) {
434             mDetailsTreeViewer.setContentProvider(mContentProviderByLibrary);
435         } else {
436             mDetailsTreeViewer.setContentProvider(mContentProviderByAllocations);
437         }
438     }
439 
updateDisplayForZygotes()440     private void updateDisplayForZygotes() {
441         boolean displayZygoteMemory = mShowZygoteAllocationsButton.getSelection();
442         mPrefStore.setValue(PREFS_SHOW_ZYGOTE_ALLOCATIONS, displayZygoteMemory);
443 
444         // inform the content providers of the zygote display setting
445         mContentProviderByLibrary.displayZygoteMemory(displayZygoteMemory);
446         mContentProviderByAllocations.displayZygoteMemory(displayZygoteMemory);
447 
448         // refresh the UI
449         mDetailsTreeViewer.refresh();
450     }
451 
updateSnapshotIndexCombo()452     private void updateSnapshotIndexCombo() {
453         List<String> items = new ArrayList<String>();
454 
455         int numSnapshots = mNativeHeapSnapshots.size();
456         for (int i = 0; i < numSnapshots; i++) {
457             // offset indices by 1 so that users see index starting at 1 rather than 0
458             items.add("Snapshot " + (i + 1));
459         }
460 
461         mSnapshotIndexCombo.setItems(items.toArray(new String[0]));
462 
463         if (numSnapshots > 0) {
464             mSnapshotIndexCombo.setEnabled(true);
465             mSnapshotIndexCombo.select(numSnapshots - 1);
466         } else {
467             mSnapshotIndexCombo.setEnabled(false);
468         }
469     }
470 
updateToolbars()471     private void updateToolbars() {
472         int numSnapshots = mNativeHeapSnapshots.size();
473         mExportHeapDataButton.setEnabled(numSnapshots > 0);
474     }
475 
476     @Override
createControl(Composite parent)477     protected Control createControl(Composite parent) {
478         parent.setLayout(new GridLayout(1, false));
479 
480         Composite c = new Composite(parent, SWT.NONE);
481         c.setLayout(new GridLayout(1, false));
482         c.setLayoutData(new GridData(GridData.FILL_BOTH));
483 
484         createControlsSection(c);
485         createDetailsSection(c);
486 
487         // Initialize widget state based on whether a client
488         // is selected or not.
489         clientSelected();
490 
491         return c;
492     }
493 
createControlsSection(Composite parent)494     private void createControlsSection(Composite parent) {
495         Composite c = new Composite(parent, SWT.NONE);
496         c.setLayout(new GridLayout(3, false));
497         c.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
498 
499         createGetHeapDataSection(c);
500 
501         Label l = new Label(c, SWT.SEPARATOR | SWT.VERTICAL);
502         l.setLayoutData(new GridData(GridData.FILL_VERTICAL));
503 
504         createDisplaySection(c);
505     }
506 
createGetHeapDataSection(Composite parent)507     private void createGetHeapDataSection(Composite parent) {
508         Composite c = new Composite(parent, SWT.NONE);
509         c.setLayout(new GridLayout(1, false));
510 
511         createTakeHeapSnapshotButton(c);
512 
513         Label l = new Label(c, SWT.SEPARATOR | SWT.HORIZONTAL);
514         l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
515 
516         createLoadHeapDataButton(c);
517     }
518 
createTakeHeapSnapshotButton(Composite parent)519     private void createTakeHeapSnapshotButton(Composite parent) {
520         mSnapshotHeapButton = new Button(parent, SWT.BORDER | SWT.PUSH);
521         mSnapshotHeapButton.setText(SNAPSHOT_HEAP_BUTTON_TEXT);
522         mSnapshotHeapButton.setLayoutData(new GridData());
523 
524         // disable by default, enabled only when a client is selected
525         mSnapshotHeapButton.setEnabled(false);
526 
527         mSnapshotHeapButton.addSelectionListener(new SelectionAdapter() {
528             @Override
529             public void widgetSelected(SelectionEvent evt) {
530                 snapshotHeap();
531             }
532         });
533     }
534 
snapshotHeap()535     private void snapshotHeap() {
536         Client c = getCurrentClient();
537         assert c != null : "Snapshot Heap could not have been enabled w/o a selected client.";
538 
539         // send an async request
540         c.requestNativeHeapInformation();
541     }
542 
createLoadHeapDataButton(Composite parent)543     private void createLoadHeapDataButton(Composite parent) {
544         mLoadHeapDataButton = new Button(parent, SWT.BORDER | SWT.PUSH);
545         mLoadHeapDataButton.setText(LOAD_HEAP_DATA_BUTTON_TEXT);
546         mLoadHeapDataButton.setLayoutData(new GridData());
547 
548         // disable by default, enabled only when a client is selected
549         mLoadHeapDataButton.setEnabled(false);
550 
551         mLoadHeapDataButton.addSelectionListener(new SelectionAdapter() {
552             @Override
553             public void widgetSelected(SelectionEvent evt) {
554                 loadHeapDataFromFile();
555             }
556         });
557     }
558 
loadHeapDataFromFile()559     private void loadHeapDataFromFile() {
560         // pop up a file dialog and get the file to load
561         final String path = getHeapDumpToImport();
562         if (path == null) {
563             return;
564         }
565 
566         Reader reader = null;
567         try {
568             reader = new FileReader(path);
569         } catch (FileNotFoundException e) {
570             // cannot occur since user input was via a FileDialog
571         }
572 
573         Shell shell = Display.getDefault().getActiveShell();
574         ProgressMonitorDialog d = new ProgressMonitorDialog(shell);
575 
576         NativeHeapDataImporter importer = new NativeHeapDataImporter(reader);
577         try {
578             d.run(true, true, importer);
579         } catch (InvocationTargetException e) {
580             // exception while parsing, display error to user and then return
581             MessageDialog.openError(shell,
582                     "Error Importing Heap Data",
583                     e.getCause().getMessage());
584             return;
585         } catch (InterruptedException e) {
586             // operation cancelled by user, simply return
587             return;
588         }
589 
590         NativeHeapSnapshot snapshot = importer.getImportedSnapshot();
591 
592         addToImportedSnapshots(snapshot);   // save imported snapshot for future use
593         addNativeHeapSnapshot(snapshot); // add to currently displayed snapshots as well
594 
595         updateDisplay();
596     }
597 
addToImportedSnapshots(NativeHeapSnapshot snapshot)598     private void addToImportedSnapshots(NativeHeapSnapshot snapshot) {
599         Client c = getCurrentClient();
600 
601         if (c == null) {
602             return;
603         }
604 
605         Integer pid = c.getClientData().getPid();
606         List<NativeHeapSnapshot> importedSnapshots = mImportedSnapshotsPerPid.get(pid);
607         if (importedSnapshots == null) {
608             importedSnapshots = new ArrayList<NativeHeapSnapshot>();
609         }
610 
611         importedSnapshots.add(snapshot);
612         mImportedSnapshotsPerPid.put(pid, importedSnapshots);
613     }
614 
getHeapDumpToImport()615     private String getHeapDumpToImport() {
616         FileDialog fileDialog = new FileDialog(Display.getDefault().getActiveShell(),
617                 SWT.OPEN);
618 
619         fileDialog.setText("Import Heap Dump");
620         fileDialog.setFilterExtensions(new String[] {"*.txt"});
621         fileDialog.setFilterPath(mPrefStore.getString(PREFS_LAST_IMPORTED_HEAPPATH));
622 
623         String selectedFile = fileDialog.open();
624         if (selectedFile != null) {
625             // save the path to restore in future dialog open
626             mPrefStore.setValue(PREFS_LAST_IMPORTED_HEAPPATH, new File(selectedFile).getParent());
627         }
628         return selectedFile;
629     }
630 
createDisplaySection(Composite parent)631     private void createDisplaySection(Composite parent) {
632         Composite c = new Composite(parent, SWT.NONE);
633         c.setLayout(new GridLayout(2, false));
634         c.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
635 
636         // Create: Display: __________________
637         createLabel(c, "Display:");
638         mSnapshotIndexCombo = new Combo(c, SWT.NONE | SWT.READ_ONLY);
639         mSnapshotIndexCombo.setItems(new String[] {"No heap snapshots available."});
640         mSnapshotIndexCombo.setEnabled(false);
641         mSnapshotIndexCombo.addSelectionListener(new SelectionAdapter() {
642             @Override
643             public void widgetSelected(SelectionEvent arg0) {
644                 displaySelectedSnapshot();
645             }
646         });
647 
648         // Create: Memory Allocated (bytes): _________________
649         createLabel(c, "Memory Allocated:");
650         mMemoryAllocatedText = new Label(c, SWT.NONE);
651         GridData gd = new GridData();
652         gd.widthHint = 100;
653         mMemoryAllocatedText.setLayoutData(gd);
654 
655         // Create: Search Path: __________________
656         createLabel(c, SYMBOL_SEARCH_PATH_LABEL_TEXT);
657         mSymbolSearchPathText = new Text(c, SWT.BORDER);
658         mSymbolSearchPathText.setMessage(SYMBOL_SEARCH_PATH_TEXT_MESSAGE);
659         mSymbolSearchPathText.setToolTipText(SYMBOL_SEARCH_PATH_TOOLTIP_TEXT);
660         mSymbolSearchPathText.addModifyListener(new ModifyListener() {
661             @Override
662             public void modifyText(ModifyEvent arg0) {
663                 String path = mSymbolSearchPathText.getText();
664                 updateSearchPath(path);
665                 mPrefStore.setValue(PREFS_SYMBOL_SEARCH_PATH, path);
666             }
667         });
668         mSymbolSearchPathText.setText(mPrefStore.getString(PREFS_SYMBOL_SEARCH_PATH));
669         mSymbolSearchPathText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
670     }
671 
updateSearchPath(String path)672     private void updateSearchPath(String path) {
673         Addr2Line.setSearchPath(path);
674     }
675 
createLabel(Composite parent, String text)676     private void createLabel(Composite parent, String text) {
677         Label l = new Label(parent, SWT.NONE);
678         l.setText(text);
679         GridData gd = new GridData();
680         gd.horizontalAlignment = SWT.RIGHT;
681         l.setLayoutData(gd);
682     }
683 
684     /**
685      * Create the details section displaying the details table and the stack trace
686      * corresponding to the selection.
687      *
688      * The details is laid out like so:
689      *   Details Toolbar
690      *   Details Table
691      *   ------------sash---
692      *   Stack Trace Label
693      *   Stack Trace Text
694      * There is a sash in between the two sections, and we need to save/restore the sash
695      * preferences. Using FormLayout seems like the easiest solution here, but the layout
696      * code looks ugly as a result.
697      */
createDetailsSection(Composite parent)698     private void createDetailsSection(Composite parent) {
699         final Composite c = new Composite(parent, SWT.NONE);
700         c.setLayout(new FormLayout());
701         c.setLayoutData(new GridData(GridData.FILL_BOTH));
702 
703         mDetailsToolBar = new ToolBar(c, SWT.FLAT | SWT.BORDER);
704         initializeDetailsToolBar(mDetailsToolBar);
705 
706         Tree detailsTree = new Tree(c, SWT.VIRTUAL | SWT.BORDER | SWT.MULTI);
707         initializeDetailsTree(detailsTree);
708 
709         final Sash sash = new Sash(c, SWT.HORIZONTAL | SWT.BORDER);
710 
711         Label stackTraceLabel = new Label(c, SWT.NONE);
712         stackTraceLabel.setText("Stack Trace:");
713 
714         Tree stackTraceTree = new Tree(c, SWT.BORDER | SWT.MULTI);
715         initializeStackTraceTree(stackTraceTree);
716 
717         // layout the widgets created above
718         FormData data = new FormData();
719         data.top    = new FormAttachment(0, 0);
720         data.left   = new FormAttachment(0, 0);
721         data.right  = new FormAttachment(100, 0);
722         mDetailsToolBar.setLayoutData(data);
723 
724         data = new FormData();
725         data.top    = new FormAttachment(mDetailsToolBar, 0);
726         data.bottom = new FormAttachment(sash, 0);
727         data.left   = new FormAttachment(0, 0);
728         data.right  = new FormAttachment(100, 0);
729         detailsTree.setLayoutData(data);
730 
731         final FormData sashData = new FormData();
732         sashData.top    = new FormAttachment(mPrefStore.getInt(PREFS_SASH_HEIGHT_PERCENT), 0);
733         sashData.left   = new FormAttachment(0, 0);
734         sashData.right  = new FormAttachment(100, 0);
735         sash.setLayoutData(sashData);
736 
737         data = new FormData();
738         data.top    = new FormAttachment(sash, 0);
739         data.left   = new FormAttachment(0, 0);
740         data.right  = new FormAttachment(100, 0);
741         stackTraceLabel.setLayoutData(data);
742 
743         data = new FormData();
744         data.top    = new FormAttachment(stackTraceLabel, 0);
745         data.left   = new FormAttachment(0, 0);
746         data.bottom = new FormAttachment(100, 0);
747         data.right  = new FormAttachment(100, 0);
748         stackTraceTree.setLayoutData(data);
749 
750         sash.addListener(SWT.Selection, new Listener() {
751             @Override
752             public void handleEvent(Event e) {
753                 Rectangle sashRect = sash.getBounds();
754                 Rectangle panelRect = c.getClientArea();
755                 int sashPercent = sashRect.y * 100 / panelRect.height;
756                 mPrefStore.setValue(PREFS_SASH_HEIGHT_PERCENT, sashPercent);
757 
758                 sashData.top = new FormAttachment(0, e.y);
759                 c.layout();
760             }
761         });
762     }
763 
initializeDetailsToolBar(ToolBar toolbar)764     private void initializeDetailsToolBar(ToolBar toolbar) {
765         mGroupByButton = new ToolItem(toolbar, SWT.CHECK);
766         mGroupByButton.setImage(ImageLoader.getDdmUiLibLoader().loadImage(GROUPBY_IMAGE,
767                 toolbar.getDisplay()));
768         mGroupByButton.setToolTipText(TOOLTIP_GROUPBY);
769         mGroupByButton.setSelection(mPrefStore.getBoolean(PREFS_GROUP_BY_LIBRARY));
770         mGroupByButton.addSelectionListener(new SelectionAdapter() {
771             @Override
772             public void widgetSelected(SelectionEvent arg0) {
773                 updateDisplayGrouping();
774             }
775         });
776 
777         mDiffsOnlyButton = new ToolItem(toolbar, SWT.CHECK);
778         mDiffsOnlyButton.setImage(ImageLoader.getDdmUiLibLoader().loadImage(DIFFS_ONLY_IMAGE,
779                 toolbar.getDisplay()));
780         mDiffsOnlyButton.setToolTipText(TOOLTIP_DIFFS_ONLY);
781         mDiffsOnlyButton.setSelection(mPrefStore.getBoolean(PREFS_SHOW_DIFFS_ONLY));
782         mDiffsOnlyButton.addSelectionListener(new SelectionAdapter() {
783             @Override
784             public void widgetSelected(SelectionEvent arg0) {
785                 // simply refresh the display, as the display logic takes care of
786                 // the current state of the diffs only checkbox.
787                 int idx = mSnapshotIndexCombo.getSelectionIndex();
788                 displaySnapshot(idx);
789             }
790         });
791 
792         mShowZygoteAllocationsButton = new ToolItem(toolbar, SWT.CHECK);
793         mShowZygoteAllocationsButton.setImage(ImageLoader.getDdmUiLibLoader().loadImage(
794                 ZYGOTE_IMAGE, toolbar.getDisplay()));
795         mShowZygoteAllocationsButton.setToolTipText(TOOLTIP_ZYGOTE_ALLOCATIONS);
796         mShowZygoteAllocationsButton.setSelection(
797                 mPrefStore.getBoolean(PREFS_SHOW_ZYGOTE_ALLOCATIONS));
798         mShowZygoteAllocationsButton.addSelectionListener(new SelectionAdapter() {
799             @Override
800             public void widgetSelected(SelectionEvent arg0) {
801                 updateDisplayForZygotes();
802             }
803         });
804 
805         mExportHeapDataButton = new ToolItem(toolbar, SWT.PUSH);
806         mExportHeapDataButton.setImage(ImageLoader.getDdmUiLibLoader().loadImage(
807                 EXPORT_DATA_IMAGE, toolbar.getDisplay()));
808         mExportHeapDataButton.setToolTipText(TOOLTIP_EXPORT_DATA);
809         mExportHeapDataButton.addSelectionListener(new SelectionAdapter() {
810             @Override
811             public void widgetSelected(SelectionEvent arg0) {
812                 exportSnapshot();
813             }
814         });
815     }
816 
817     /** Export currently displayed snapshot to a file */
exportSnapshot()818     private void exportSnapshot() {
819         int idx = mSnapshotIndexCombo.getSelectionIndex();
820         String snapshotName = mSnapshotIndexCombo.getItem(idx);
821 
822         FileDialog fileDialog = new FileDialog(Display.getDefault().getActiveShell(),
823                 SWT.SAVE);
824 
825         fileDialog.setText("Save " + snapshotName);
826         fileDialog.setFileName("allocations.txt");
827 
828         final String fileName = fileDialog.open();
829         if (fileName == null) {
830             return;
831         }
832 
833         final NativeHeapSnapshot snapshot = mNativeHeapSnapshots.get(idx);
834         Thread t = new Thread(new Runnable() {
835             @Override
836             public void run() {
837                 PrintWriter out;
838                 try {
839                     out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
840                 } catch (IOException e) {
841                     displayErrorMessage(e.getMessage());
842                     return;
843                 }
844 
845                 for (NativeAllocationInfo alloc : snapshot.getAllocations()) {
846                     out.println(alloc.toString());
847                 }
848                 out.close();
849             }
850 
851             private void displayErrorMessage(final String message) {
852                 Display.getDefault().syncExec(new Runnable() {
853                     @Override
854                     public void run() {
855                         MessageDialog.openError(Display.getDefault().getActiveShell(),
856                                 "Failed to export heap data", message);
857                     }
858                 });
859             }
860         });
861         t.setName("Saving Heap Data to File...");
862         t.start();
863     }
864 
initializeDetailsTree(Tree tree)865     private void initializeDetailsTree(Tree tree) {
866         tree.setHeaderVisible(true);
867         tree.setLinesVisible(true);
868 
869         List<String> properties = Arrays.asList(new String[] {
870                 "Library",
871                 "Total",
872                 "Percentage",
873                 "Count",
874                 "Size",
875                 "Method",
876         });
877 
878         List<String> sampleValues = Arrays.asList(new String[] {
879                 "/path/in/device/to/system/library.so",
880                 "123456789",
881                 " 100%",
882                 "123456789",
883                 "123456789",
884                 "PossiblyLongDemangledMethodName",
885         });
886 
887         // right align numeric values
888         List<Integer> swtFlags = Arrays.asList(new Integer[] {
889                 SWT.LEFT,
890                 SWT.RIGHT,
891                 SWT.RIGHT,
892                 SWT.RIGHT,
893                 SWT.RIGHT,
894                 SWT.LEFT,
895         });
896 
897         for (int i = 0; i < properties.size(); i++) {
898             String p = properties.get(i);
899             String v = sampleValues.get(i);
900             int flags = swtFlags.get(i);
901             TableHelper.createTreeColumn(tree, p, flags, v, getPref("details", p), mPrefStore);
902         }
903 
904         mDetailsTreeViewer = new TreeViewer(tree);
905 
906         mDetailsTreeViewer.setUseHashlookup(true);
907 
908         boolean displayZygotes = mPrefStore.getBoolean(PREFS_SHOW_ZYGOTE_ALLOCATIONS);
909         mContentProviderByAllocations = new NativeHeapProviderByAllocations(mDetailsTreeViewer,
910                 displayZygotes);
911         mContentProviderByLibrary = new NativeHeapProviderByLibrary(mDetailsTreeViewer,
912                 displayZygotes);
913         if (mPrefStore.getBoolean(PREFS_GROUP_BY_LIBRARY)) {
914             mDetailsTreeViewer.setContentProvider(mContentProviderByLibrary);
915         } else {
916             mDetailsTreeViewer.setContentProvider(mContentProviderByAllocations);
917         }
918 
919         mDetailsTreeLabelProvider = new NativeHeapLabelProvider();
920         mDetailsTreeViewer.setLabelProvider(mDetailsTreeLabelProvider);
921 
922         mDetailsTreeViewer.setInput(null);
923 
924         tree.addSelectionListener(new SelectionAdapter() {
925             @Override
926             public void widgetSelected(SelectionEvent event) {
927                 displayStackTraceForSelection();
928             }
929         });
930     }
931 
initializeStackTraceTree(Tree tree)932     private void initializeStackTraceTree(Tree tree) {
933         tree.setHeaderVisible(true);
934         tree.setLinesVisible(true);
935 
936         List<String> properties = Arrays.asList(new String[] {
937                 "Address",
938                 "Library",
939                 "Method",
940                 "File",
941                 "Line",
942         });
943 
944         List<String> sampleValues = Arrays.asList(new String[] {
945                 "0x1234_5678",
946                 "/path/in/device/to/system/library.so",
947                 "PossiblyLongDemangledMethodName",
948                 "/android/out/prefix/in/home/directory/to/path/in/device/to/system/library.so",
949                 "2000",
950         });
951 
952         for (int i = 0; i < properties.size(); i++) {
953             String p = properties.get(i);
954             String v = sampleValues.get(i);
955             TableHelper.createTreeColumn(tree, p, SWT.LEFT, v, getPref("stack", p), mPrefStore);
956         }
957 
958         mStackTraceTreeViewer = new TreeViewer(tree);
959 
960         mStackTraceTreeViewer.setContentProvider(new NativeStackContentProvider());
961         mStackTraceTreeViewer.setLabelProvider(new NativeStackLabelProvider());
962 
963         mStackTraceTreeViewer.setInput(null);
964     }
965 
displayStackTraceForSelection()966     private void displayStackTraceForSelection() {
967         TreeItem []items = mDetailsTreeViewer.getTree().getSelection();
968         if (items.length == 0) {
969             mStackTraceTreeViewer.setInput(null);
970             return;
971         }
972 
973         Object data = items[0].getData();
974         if (!(data instanceof NativeAllocationInfo)) {
975             mStackTraceTreeViewer.setInput(null);
976             return;
977         }
978 
979         NativeAllocationInfo info = (NativeAllocationInfo) data;
980         if (info.isStackCallResolved()) {
981             mStackTraceTreeViewer.setInput(info.getResolvedStackCall());
982         } else {
983             mStackTraceTreeViewer.setInput(info.getStackCallAddresses());
984         }
985     }
986 
getPref(String prefix, String s)987     private String getPref(String prefix, String s) {
988         return "nativeheap.tree." + prefix + "." + s;
989     }
990 
991     @Override
setFocus()992     public void setFocus() {
993     }
994 
995     private ITableFocusListener mTableFocusListener;
996 
997     @Override
setTableFocusListener(ITableFocusListener listener)998     public void setTableFocusListener(ITableFocusListener listener) {
999         mTableFocusListener = listener;
1000 
1001         final Tree heapSitesTree = mDetailsTreeViewer.getTree();
1002         final IFocusedTableActivator heapSitesActivator = new IFocusedTableActivator() {
1003             @Override
1004             public void copy(Clipboard clipboard) {
1005                 TreeItem[] items = heapSitesTree.getSelection();
1006                 copyToClipboard(items, clipboard);
1007             }
1008 
1009             @Override
1010             public void selectAll() {
1011                 heapSitesTree.selectAll();
1012             }
1013         };
1014 
1015         heapSitesTree.addFocusListener(new FocusListener() {
1016             @Override
1017             public void focusLost(FocusEvent arg0) {
1018                 mTableFocusListener.focusLost(heapSitesActivator);
1019             }
1020 
1021             @Override
1022             public void focusGained(FocusEvent arg0) {
1023                 mTableFocusListener.focusGained(heapSitesActivator);
1024             }
1025         });
1026 
1027         final Tree stackTraceTree = mStackTraceTreeViewer.getTree();
1028         final IFocusedTableActivator stackTraceActivator = new IFocusedTableActivator() {
1029             @Override
1030             public void copy(Clipboard clipboard) {
1031                 TreeItem[] items = stackTraceTree.getSelection();
1032                 copyToClipboard(items, clipboard);
1033             }
1034 
1035             @Override
1036             public void selectAll() {
1037                 stackTraceTree.selectAll();
1038             }
1039         };
1040 
1041         stackTraceTree.addFocusListener(new FocusListener() {
1042             @Override
1043             public void focusLost(FocusEvent arg0) {
1044                 mTableFocusListener.focusLost(stackTraceActivator);
1045             }
1046 
1047             @Override
1048             public void focusGained(FocusEvent arg0) {
1049                 mTableFocusListener.focusGained(stackTraceActivator);
1050             }
1051         });
1052     }
1053 
copyToClipboard(TreeItem[] items, Clipboard clipboard)1054     private void copyToClipboard(TreeItem[] items, Clipboard clipboard) {
1055         StringBuilder sb = new StringBuilder();
1056 
1057         for (TreeItem item : items) {
1058             Object data = item.getData();
1059             if (data != null) {
1060                 sb.append(data.toString());
1061                 sb.append('\n');
1062             }
1063         }
1064 
1065         String content = sb.toString();
1066         if (content.length() > 0) {
1067             clipboard.setContents(
1068                     new Object[] {sb.toString()},
1069                     new Transfer[] {TextTransfer.getInstance()}
1070                     );
1071         }
1072     }
1073 
1074     private class SymbolResolverTask implements Runnable {
1075         private List<NativeAllocationInfo> mCallSites;
1076         private List<NativeLibraryMapInfo> mMappedLibraries;
1077         private Map<Long, NativeStackCallInfo> mResolvedSymbolCache;
1078 
SymbolResolverTask(List<NativeAllocationInfo> callSites, List<NativeLibraryMapInfo> mappedLibraries)1079         public SymbolResolverTask(List<NativeAllocationInfo> callSites,
1080                 List<NativeLibraryMapInfo> mappedLibraries) {
1081             mCallSites = callSites;
1082             mMappedLibraries = mappedLibraries;
1083 
1084             mResolvedSymbolCache = new HashMap<Long, NativeStackCallInfo>();
1085         }
1086 
1087         @Override
run()1088         public void run() {
1089             for (NativeAllocationInfo callSite : mCallSites) {
1090                 if (callSite.isStackCallResolved()) {
1091                     continue;
1092                 }
1093 
1094                 List<Long> addresses = callSite.getStackCallAddresses();
1095                 List<NativeStackCallInfo> resolvedStackInfo =
1096                         new ArrayList<NativeStackCallInfo>(addresses.size());
1097 
1098                 for (Long address : addresses) {
1099                     NativeStackCallInfo info = mResolvedSymbolCache.get(address);
1100 
1101                     if (info != null) {
1102                         resolvedStackInfo.add(info);
1103                     } else {
1104                         info = resolveAddress(address);
1105                         resolvedStackInfo.add(info);
1106                         mResolvedSymbolCache.put(address, info);
1107                     }
1108                 }
1109 
1110                 callSite.setResolvedStackCall(resolvedStackInfo);
1111             }
1112 
1113             Display.getDefault().asyncExec(new Runnable() {
1114                 @Override
1115                 public void run() {
1116                     mDetailsTreeViewer.refresh();
1117                     mStackTraceTreeViewer.refresh();
1118                 }
1119             });
1120         }
1121 
resolveAddress(long addr)1122         private NativeStackCallInfo resolveAddress(long addr) {
1123             NativeLibraryMapInfo library = getLibraryFor(addr);
1124 
1125             if (library != null) {
1126                 Addr2Line process = Addr2Line.getProcess(library);
1127                 if (process != null) {
1128                     NativeStackCallInfo info = process.getAddress(addr);
1129                     if (info != null) {
1130                         return info;
1131                     }
1132                 }
1133             }
1134 
1135             return new NativeStackCallInfo(addr,
1136                     library != null ? library.getLibraryName() : null,
1137                     Long.toHexString(addr),
1138                     "");
1139         }
1140 
getLibraryFor(long addr)1141         private NativeLibraryMapInfo getLibraryFor(long addr) {
1142             for (NativeLibraryMapInfo info : mMappedLibraries) {
1143                 if (info.isWithinLibrary(addr)) {
1144                     return info;
1145                 }
1146             }
1147 
1148             Log.d("ddm-nativeheap", "Failed finding Library for " + Long.toHexString(addr));
1149             return null;
1150         }
1151     }
1152 }
1153