OSDN Git Service

Merge "Add eclipse version to usage stat ping."
[android-x86/sdk.git] / ddms / libs / ddmuilib / src / com / android / ddmuilib / NativeHeapPanel.java
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.ddmuilib;
18
19 import com.android.ddmlib.Client;
20 import com.android.ddmlib.ClientData;
21 import com.android.ddmlib.Log;
22 import com.android.ddmlib.NativeAllocationInfo;
23 import com.android.ddmlib.NativeLibraryMapInfo;
24 import com.android.ddmlib.NativeStackCallInfo;
25 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
26 import com.android.ddmlib.HeapSegment.HeapSegmentElement;
27 import com.android.ddmuilib.annotation.WorkerThread;
28
29 import org.eclipse.jface.preference.IPreferenceStore;
30 import org.eclipse.swt.SWT;
31 import org.eclipse.swt.SWTException;
32 import org.eclipse.swt.custom.StackLayout;
33 import org.eclipse.swt.events.SelectionAdapter;
34 import org.eclipse.swt.events.SelectionEvent;
35 import org.eclipse.swt.graphics.Image;
36 import org.eclipse.swt.graphics.ImageData;
37 import org.eclipse.swt.graphics.PaletteData;
38 import org.eclipse.swt.graphics.RGB;
39 import org.eclipse.swt.graphics.Rectangle;
40 import org.eclipse.swt.layout.FormAttachment;
41 import org.eclipse.swt.layout.FormData;
42 import org.eclipse.swt.layout.FormLayout;
43 import org.eclipse.swt.layout.GridData;
44 import org.eclipse.swt.layout.GridLayout;
45 import org.eclipse.swt.widgets.Button;
46 import org.eclipse.swt.widgets.Combo;
47 import org.eclipse.swt.widgets.Composite;
48 import org.eclipse.swt.widgets.Control;
49 import org.eclipse.swt.widgets.Display;
50 import org.eclipse.swt.widgets.Event;
51 import org.eclipse.swt.widgets.FileDialog;
52 import org.eclipse.swt.widgets.Label;
53 import org.eclipse.swt.widgets.Listener;
54 import org.eclipse.swt.widgets.Sash;
55 import org.eclipse.swt.widgets.Table;
56 import org.eclipse.swt.widgets.TableItem;
57
58 import java.io.BufferedWriter;
59 import java.io.FileWriter;
60 import java.io.IOException;
61 import java.io.PrintWriter;
62 import java.text.DecimalFormat;
63 import java.text.NumberFormat;
64 import java.util.ArrayList;
65 import java.util.Collections;
66 import java.util.Comparator;
67 import java.util.HashMap;
68 import java.util.Iterator;
69
70 /**
71  * Panel with native heap information.
72  */
73 public final class NativeHeapPanel extends BaseHeapPanel {
74
75     /** color palette and map legend. NATIVE is the last enum is a 0 based enum list, so we need
76      * Native+1 at least. We also need 2 more entries for free area and expansion area.  */
77     private static final int NUM_PALETTE_ENTRIES = HeapSegmentElement.KIND_NATIVE+2 +1;
78     private static final String[] mMapLegend = new String[NUM_PALETTE_ENTRIES];
79     private static final PaletteData mMapPalette = createPalette();
80
81     private static final int ALLOC_DISPLAY_ALL = 0;
82     private static final int ALLOC_DISPLAY_PRE_ZYGOTE = 1;
83     private static final int ALLOC_DISPLAY_POST_ZYGOTE = 2;
84
85     private Display mDisplay;
86
87     private Composite mBase;
88
89     private Label mUpdateStatus;
90
91     /** combo giving choice of what to display: all, pre-zygote, post-zygote */
92     private Combo mAllocDisplayCombo;
93
94     private Button mFullUpdateButton;
95
96     // see CreateControl()
97     //private Button mDiffUpdateButton;
98
99     private Combo mDisplayModeCombo;
100
101     /** stack composite for mode (1-2) & 3 */
102     private Composite mTopStackComposite;
103
104     private StackLayout mTopStackLayout;
105
106     /** stack composite for mode 1 & 2 */
107     private Composite mAllocationStackComposite;
108
109     private StackLayout mAllocationStackLayout;
110
111     /** top level container for mode 1 & 2 */
112     private Composite mTableModeControl;
113
114     /** top level object for the allocation mode */
115     private Control mAllocationModeTop;
116
117     /** top level for the library mode */
118     private Control mLibraryModeTopControl;
119
120     /** composite for page UI and total memory display */
121     private Composite mPageUIComposite;
122
123     private Label mTotalMemoryLabel;
124
125     private Label mPageLabel;
126
127     private Button mPageNextButton;
128
129     private Button mPagePreviousButton;
130
131     private Table mAllocationTable;
132
133     private Table mLibraryTable;
134
135     private Table mLibraryAllocationTable;
136
137     private Table mDetailTable;
138
139     private Label mImage;
140
141     private int mAllocDisplayMode = ALLOC_DISPLAY_ALL;
142
143     /**
144      * pointer to current stackcall thread computation in order to quit it if
145      * required (new update requested)
146      */
147     private StackCallThread mStackCallThread;
148
149     /** Current Library Allocation table fill thread. killed if selection changes */
150     private FillTableThread mFillTableThread;
151
152     /**
153      * current client data. Used to access the malloc info when switching pages
154      * or selecting allocation to show stack call
155      */
156     private ClientData mClientData;
157
158     /**
159      * client data from a previous display. used when asking for an "update & diff"
160      */
161     private ClientData mBackUpClientData;
162
163     /** list of NativeAllocationInfo objects filled with the list from ClientData */
164     private final ArrayList<NativeAllocationInfo> mAllocations =
165         new ArrayList<NativeAllocationInfo>();
166
167     /** list of the {@link NativeAllocationInfo} being displayed based on the selection
168      * of {@link #mAllocDisplayCombo}.
169      */
170     private final ArrayList<NativeAllocationInfo> mDisplayedAllocations =
171         new ArrayList<NativeAllocationInfo>();
172
173     /** list of NativeAllocationInfo object kept as backup when doing an "update & diff" */
174     private final ArrayList<NativeAllocationInfo> mBackUpAllocations =
175         new ArrayList<NativeAllocationInfo>();
176
177     /** back up of the total memory, used when doing an "update & diff" */
178     private int mBackUpTotalMemory;
179
180     private int mCurrentPage = 0;
181
182     private int mPageCount = 0;
183
184     /**
185      * list of allocation per Library. This is created from the list of
186      * NativeAllocationInfo objects that is stored in the ClientData object. Since we
187      * don't keep this list around, it is recomputed everytime the client
188      * changes.
189      */
190     private final ArrayList<LibraryAllocations> mLibraryAllocations =
191         new ArrayList<LibraryAllocations>();
192
193     /* args to setUpdateStatus() */
194     private static final int NOT_SELECTED = 0;
195
196     private static final int NOT_ENABLED = 1;
197
198     private static final int ENABLED = 2;
199
200     private static final int DISPLAY_PER_PAGE = 20;
201
202     private static final String PREFS_ALLOCATION_SASH = "NHallocSash"; //$NON-NLS-1$
203     private static final String PREFS_LIBRARY_SASH = "NHlibrarySash"; //$NON-NLS-1$
204     private static final String PREFS_DETAIL_ADDRESS = "NHdetailAddress"; //$NON-NLS-1$
205     private static final String PREFS_DETAIL_LIBRARY = "NHdetailLibrary"; //$NON-NLS-1$
206     private static final String PREFS_DETAIL_METHOD = "NHdetailMethod"; //$NON-NLS-1$
207     private static final String PREFS_DETAIL_FILE = "NHdetailFile"; //$NON-NLS-1$
208     private static final String PREFS_DETAIL_LINE = "NHdetailLine"; //$NON-NLS-1$
209     private static final String PREFS_ALLOC_TOTAL = "NHallocTotal"; //$NON-NLS-1$
210     private static final String PREFS_ALLOC_COUNT = "NHallocCount"; //$NON-NLS-1$
211     private static final String PREFS_ALLOC_SIZE = "NHallocSize"; //$NON-NLS-1$
212     private static final String PREFS_ALLOC_LIBRARY = "NHallocLib"; //$NON-NLS-1$
213     private static final String PREFS_ALLOC_METHOD = "NHallocMethod"; //$NON-NLS-1$
214     private static final String PREFS_ALLOC_FILE = "NHallocFile"; //$NON-NLS-1$
215     private static final String PREFS_LIB_LIBRARY = "NHlibLibrary"; //$NON-NLS-1$
216     private static final String PREFS_LIB_SIZE = "NHlibSize"; //$NON-NLS-1$
217     private static final String PREFS_LIB_COUNT = "NHlibCount"; //$NON-NLS-1$
218     private static final String PREFS_LIBALLOC_TOTAL = "NHlibAllocTotal"; //$NON-NLS-1$
219     private static final String PREFS_LIBALLOC_COUNT = "NHlibAllocCount"; //$NON-NLS-1$
220     private static final String PREFS_LIBALLOC_SIZE = "NHlibAllocSize"; //$NON-NLS-1$
221     private static final String PREFS_LIBALLOC_METHOD = "NHlibAllocMethod"; //$NON-NLS-1$
222
223     /** static formatter object to format all numbers as #,### */
224     private static DecimalFormat sFormatter;
225     static {
226         sFormatter = (DecimalFormat)NumberFormat.getInstance();
227         if (sFormatter != null)
228             sFormatter = new DecimalFormat("#,###");
229         else
230             sFormatter.applyPattern("#,###");
231     }
232
233
234     /**
235      * caching mechanism to avoid recomputing the backtrace for a particular
236      * address several times.
237      */
238     private HashMap<Long, NativeStackCallInfo> mSourceCache =
239         new HashMap<Long,NativeStackCallInfo>();
240     private long mTotalSize;
241     private Button mSaveButton;
242     private Button mSymbolsButton;
243
244     /**
245      * thread class to convert the address call into method, file and line
246      * number in the background.
247      */
248     private class StackCallThread extends BackgroundThread {
249         private ClientData mClientData;
250
251         public StackCallThread(ClientData cd) {
252             mClientData = cd;
253         }
254
255         public ClientData getClientData() {
256             return mClientData;
257         }
258
259         @Override
260         public void run() {
261             // loop through all the NativeAllocationInfo and init them
262             Iterator<NativeAllocationInfo> iter = mAllocations.iterator();
263             int total = mAllocations.size();
264             int count = 0;
265             while (iter.hasNext()) {
266
267                 if (isQuitting())
268                     return;
269
270                 NativeAllocationInfo info = iter.next();
271                 if (info.isStackCallResolved() == false) {
272                     final Long[] list = info.getStackCallAddresses();
273                     final int size = list.length;
274
275                     ArrayList<NativeStackCallInfo> resolvedStackCall =
276                         new ArrayList<NativeStackCallInfo>();
277
278                     for (int i = 0; i < size; i++) {
279                         long addr = list[i];
280
281                         // first check if the addr has already been converted.
282                         NativeStackCallInfo source = mSourceCache.get(addr);
283
284                         // if not we convert it
285                         if (source == null) {
286                             source = sourceForAddr(addr);
287                             mSourceCache.put(addr, source);
288                         }
289
290                         resolvedStackCall.add(source);
291                     }
292
293                     info.setResolvedStackCall(resolvedStackCall);
294                 }
295                 // after every DISPLAY_PER_PAGE we ask for a ui refresh, unless
296                 // we reach total, since we also do it after the loop
297                 // (only an issue in case we have a perfect number of page)
298                 count++;
299                 if ((count % DISPLAY_PER_PAGE) == 0 && count != total) {
300                     if (updateNHAllocationStackCalls(mClientData, count) == false) {
301                         // looks like the app is quitting, so we just
302                         // stopped the thread
303                         return;
304                     }
305                 }
306             }
307
308             updateNHAllocationStackCalls(mClientData, count);
309         }
310
311         private NativeStackCallInfo sourceForAddr(long addr) {
312             NativeLibraryMapInfo library = getLibraryFor(addr);
313
314             if (library != null) {
315
316                 Addr2Line process = Addr2Line.getProcess(library.getLibraryName());
317                 if (process != null) {
318                     // remove the base of the library address
319                     long value = addr - library.getStartAddress();
320                     NativeStackCallInfo info = process.getAddress(value);
321                     if (info != null) {
322                         return info;
323                     }
324                 }
325             }
326
327             return new NativeStackCallInfo(library != null ? library.getLibraryName() : null,
328                     Long.toHexString(addr), "");
329         }
330
331         private NativeLibraryMapInfo getLibraryFor(long addr) {
332             Iterator<NativeLibraryMapInfo> it = mClientData.getNativeLibraryMapInfo();
333
334             while (it.hasNext()) {
335                 NativeLibraryMapInfo info = it.next();
336
337                 if (info.isWithinLibrary(addr)) {
338                     return info;
339                 }
340             }
341
342             Log.d("ddm-nativeheap", "Failed finding Library for " + Long.toHexString(addr));
343             return null;
344         }
345
346         /**
347          * update the Native Heap panel with the amount of allocation for which the
348          * stack call has been computed. This is called from a non UI thread, but
349          * will be executed in the UI thread.
350          *
351          * @param count the amount of allocation
352          * @return false if the display was disposed and the update couldn't happen
353          */
354         private boolean updateNHAllocationStackCalls(final ClientData clientData, final int count) {
355             if (mDisplay.isDisposed() == false) {
356                 mDisplay.asyncExec(new Runnable() {
357                     public void run() {
358                         updateAllocationStackCalls(clientData, count);
359                     }
360                 });
361                 return true;
362             }
363             return false;
364         }
365     }
366
367     private class FillTableThread extends BackgroundThread {
368         private LibraryAllocations mLibAlloc;
369
370         private int mMax;
371
372         public FillTableThread(LibraryAllocations liballoc, int m) {
373             mLibAlloc = liballoc;
374             mMax = m;
375         }
376
377         @Override
378         public void run() {
379             for (int i = mMax; i > 0 && isQuitting() == false; i -= 10) {
380                 updateNHLibraryAllocationTable(mLibAlloc, mMax - i, mMax - i + 10);
381             }
382         }
383
384         /**
385          * updates the library allocation table in the Native Heap panel. This is
386          * called from a non UI thread, but will be executed in the UI thread.
387          *
388          * @param liballoc the current library allocation object being displayed
389          * @param start start index of items that need to be displayed
390          * @param end end index of the items that need to be displayed
391          */
392         private void updateNHLibraryAllocationTable(final LibraryAllocations libAlloc,
393                 final int start, final int end) {
394             if (mDisplay.isDisposed() == false) {
395                 mDisplay.asyncExec(new Runnable() {
396                     public void run() {
397                         updateLibraryAllocationTable(libAlloc, start, end);
398                     }
399                 });
400             }
401
402         }
403     }
404
405     /** class to aggregate allocations per library */
406     public static class LibraryAllocations {
407         private String mLibrary;
408
409         private final ArrayList<NativeAllocationInfo> mLibAllocations =
410             new ArrayList<NativeAllocationInfo>();
411
412         private int mSize;
413
414         private int mCount;
415
416         /** construct the aggregate object for a library */
417         public LibraryAllocations(final String lib) {
418             mLibrary = lib;
419         }
420
421         /** get the library name */
422         public String getLibrary() {
423             return mLibrary;
424         }
425
426         /** add a NativeAllocationInfo object to this aggregate object */
427         public void addAllocation(NativeAllocationInfo info) {
428             mLibAllocations.add(info);
429         }
430
431         /** get an iterator on the NativeAllocationInfo objects */
432         public Iterator<NativeAllocationInfo> getAllocations() {
433             return mLibAllocations.iterator();
434         }
435
436         /** get a NativeAllocationInfo object by index */
437         public NativeAllocationInfo getAllocation(int index) {
438             return mLibAllocations.get(index);
439         }
440
441         /** returns the NativeAllocationInfo object count */
442         public int getAllocationSize() {
443             return mLibAllocations.size();
444         }
445
446         /** returns the total allocation size */
447         public int getSize() {
448             return mSize;
449         }
450
451         /** returns the number of allocations */
452         public int getCount() {
453             return mCount;
454         }
455
456         /**
457          * compute the allocation count and size for allocation objects added
458          * through <code>addAllocation()</code>, and sort the objects by
459          * total allocation size.
460          */
461         public void computeAllocationSizeAndCount() {
462             mSize = 0;
463             mCount = 0;
464             for (NativeAllocationInfo info : mLibAllocations) {
465                 mCount += info.getAllocationCount();
466                 mSize += info.getAllocationCount() * info.getSize();
467             }
468             Collections.sort(mLibAllocations, new Comparator<NativeAllocationInfo>() {
469                 public int compare(NativeAllocationInfo o1, NativeAllocationInfo o2) {
470                     return o2.getAllocationCount() * o2.getSize() -
471                         o1.getAllocationCount() * o1.getSize();
472                 }
473             });
474         }
475     }
476
477     /**
478      * Create our control(s).
479      */
480     @Override
481     protected Control createControl(Composite parent) {
482
483         mDisplay = parent.getDisplay();
484
485         mBase = new Composite(parent, SWT.NONE);
486         GridLayout gl = new GridLayout(1, false);
487         gl.horizontalSpacing = 0;
488         gl.verticalSpacing = 0;
489         mBase.setLayout(gl);
490         mBase.setLayoutData(new GridData(GridData.FILL_BOTH));
491
492         // composite for <update btn> <status>
493         Composite tmp = new Composite(mBase, SWT.NONE);
494         tmp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
495         tmp.setLayout(gl = new GridLayout(2, false));
496         gl.marginWidth = gl.marginHeight = 0;
497
498         mFullUpdateButton = new Button(tmp, SWT.NONE);
499         mFullUpdateButton.setText("Full Update");
500         mFullUpdateButton.addSelectionListener(new SelectionAdapter() {
501             @Override
502             public void widgetSelected(SelectionEvent e) {
503                 mBackUpClientData = null;
504                 mDisplayModeCombo.setEnabled(false);
505                 mSaveButton.setEnabled(false);
506                 emptyTables();
507                 // if we already have a stack call computation for this
508                 // client
509                 // we stop it
510                 if (mStackCallThread != null &&
511                         mStackCallThread.getClientData() == mClientData) {
512                     mStackCallThread.quit();
513                     mStackCallThread = null;
514                 }
515                 mLibraryAllocations.clear();
516                 Client client = getCurrentClient();
517                 if (client != null) {
518                     client.requestNativeHeapInformation();
519                 }
520             }
521         });
522
523         mUpdateStatus = new Label(tmp, SWT.NONE);
524         mUpdateStatus.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
525
526         // top layout for the combos and oter controls on the right.
527         Composite top_layout = new Composite(mBase, SWT.NONE);
528         top_layout.setLayout(gl = new GridLayout(4, false));
529         gl.marginWidth = gl.marginHeight = 0;
530
531         new Label(top_layout, SWT.NONE).setText("Show:");
532
533         mAllocDisplayCombo = new Combo(top_layout, SWT.DROP_DOWN | SWT.READ_ONLY);
534         mAllocDisplayCombo.setLayoutData(new GridData(
535                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
536         mAllocDisplayCombo.add("All Allocations");
537         mAllocDisplayCombo.add("Pre-Zygote Allocations");
538         mAllocDisplayCombo.add("Zygote Child Allocations (Z)");
539         mAllocDisplayCombo.addSelectionListener(new SelectionAdapter() {
540             @Override
541             public void widgetSelected(SelectionEvent e) {
542                 onAllocDisplayChange();
543             }
544         });
545         mAllocDisplayCombo.select(0);
546
547         // separator
548         Label separator = new Label(top_layout, SWT.SEPARATOR | SWT.VERTICAL);
549         GridData gd;
550         separator.setLayoutData(gd = new GridData(
551                 GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
552         gd.heightHint = 0;
553         gd.verticalSpan = 2;
554
555         mSaveButton = new Button(top_layout, SWT.PUSH);
556         mSaveButton.setText("Save...");
557         mSaveButton.setEnabled(false);
558         mSaveButton.addSelectionListener(new SelectionAdapter() {
559             @Override
560             public void widgetSelected(SelectionEvent e) {
561                 FileDialog fileDialog = new FileDialog(mBase.getShell(), SWT.SAVE);
562
563                 fileDialog.setText("Save Allocations");
564                 fileDialog.setFileName("allocations.txt");
565
566                 String fileName = fileDialog.open();
567                 if (fileName != null) {
568                     saveAllocations(fileName);
569                 }
570             }
571         });
572
573         /*
574          * TODO: either fix the diff mechanism or remove it altogether.
575         mDiffUpdateButton = new Button(top_layout, SWT.NONE);
576         mDiffUpdateButton.setText("Update && Diff");
577         mDiffUpdateButton.addSelectionListener(new SelectionAdapter() {
578             @Override
579             public void widgetSelected(SelectionEvent e) {
580                 // since this is an update and diff, we need to store the
581                 // current list
582                 // of mallocs
583                 mBackUpAllocations.clear();
584                 mBackUpAllocations.addAll(mAllocations);
585                 mBackUpClientData = mClientData;
586                 mBackUpTotalMemory = mClientData.getTotalNativeMemory();
587
588                 mDisplayModeCombo.setEnabled(false);
589                 emptyTables();
590                 // if we already have a stack call computation for this
591                 // client
592                 // we stop it
593                 if (mStackCallThread != null &&
594                         mStackCallThread.getClientData() == mClientData) {
595                     mStackCallThread.quit();
596                     mStackCallThread = null;
597                 }
598                 mLibraryAllocations.clear();
599                 Client client = getCurrentClient();
600                 if (client != null) {
601                     client.requestNativeHeapInformation();
602                 }
603             }
604         });
605         */
606
607         Label l = new Label(top_layout, SWT.NONE);
608         l.setText("Display:");
609
610         mDisplayModeCombo = new Combo(top_layout, SWT.DROP_DOWN | SWT.READ_ONLY);
611         mDisplayModeCombo.setLayoutData(new GridData(
612                 GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
613         mDisplayModeCombo.setItems(new String[] { "Allocation List", "By Libraries" });
614         mDisplayModeCombo.select(0);
615         mDisplayModeCombo.addSelectionListener(new SelectionAdapter() {
616             @Override
617             public void widgetSelected(SelectionEvent e) {
618                 switchDisplayMode();
619             }
620         });
621         mDisplayModeCombo.setEnabled(false);
622
623         mSymbolsButton = new Button(top_layout, SWT.PUSH);
624         mSymbolsButton.setText("Load Symbols");
625         mSymbolsButton.setEnabled(false);
626
627
628         // create a composite that will contains the actual content composites,
629         // in stack mode layout.
630         // This top level composite contains 2 other composites.
631         // * one for both Allocations and Libraries mode
632         // * one for flat mode (which is gone for now)
633
634         mTopStackComposite = new Composite(mBase, SWT.NONE);
635         mTopStackComposite.setLayout(mTopStackLayout = new StackLayout());
636         mTopStackComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
637
638         // create 1st and 2nd modes
639         createTableDisplay(mTopStackComposite);
640
641         mTopStackLayout.topControl = mTableModeControl;
642         mTopStackComposite.layout();
643
644         setUpdateStatus(NOT_SELECTED);
645
646         // Work in progress
647         // TODO add image display of native heap.
648         //mImage = new Label(mBase, SWT.NONE);
649
650         mBase.pack();
651
652         return mBase;
653     }
654
655     /**
656      * Sets the focus to the proper control inside the panel.
657      */
658     @Override
659     public void setFocus() {
660         // TODO
661     }
662
663
664     /**
665      * Sent when an existing client information changed.
666      * <p/>
667      * This is sent from a non UI thread.
668      * @param client the updated client.
669      * @param changeMask the bit mask describing the changed properties. It can contain
670      * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
671      * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
672      * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
673      * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
674      *
675      * @see IClientChangeListener#clientChanged(Client, int)
676      */
677     public void clientChanged(final Client client, int changeMask) {
678         if (client == getCurrentClient()) {
679             if ((changeMask & Client.CHANGE_NATIVE_HEAP_DATA) == Client.CHANGE_NATIVE_HEAP_DATA) {
680                 if (mBase.isDisposed())
681                     return;
682
683                 mBase.getDisplay().asyncExec(new Runnable() {
684                     public void run() {
685                         clientSelected();
686                     }
687                 });
688             }
689         }
690     }
691
692     /**
693      * Sent when a new device is selected. The new device can be accessed
694      * with {@link #getCurrentDevice()}.
695      */
696     @Override
697     public void deviceSelected() {
698         // pass
699     }
700
701     /**
702      * Sent when a new client is selected. The new client can be accessed
703      * with {@link #getCurrentClient()}.
704      */
705     @Override
706     public void clientSelected() {
707         if (mBase.isDisposed())
708             return;
709
710         Client client = getCurrentClient();
711
712         mDisplayModeCombo.setEnabled(false);
713         emptyTables();
714
715         Log.d("ddms", "NativeHeapPanel: changed " + client);
716
717         if (client != null) {
718             ClientData cd = client.getClientData();
719             mClientData = cd;
720
721             // if (cd.getShowHeapUpdates())
722             setUpdateStatus(ENABLED);
723             // else
724             // setUpdateStatus(NOT_ENABLED);
725
726             initAllocationDisplay();
727
728             //renderBitmap(cd);
729         } else {
730             mClientData = null;
731             setUpdateStatus(NOT_SELECTED);
732         }
733
734         mBase.pack();
735     }
736
737     /**
738      * Update the UI with the newly compute stack calls, unless the UI switched
739      * to a different client.
740      *
741      * @param cd the ClientData for which the stack call are being computed.
742      * @param count the current count of allocations for which the stack calls
743      *            have been computed.
744      */
745     @WorkerThread
746     public void updateAllocationStackCalls(ClientData cd, int count) {
747         // we have to check that the panel still shows the same clientdata than
748         // the thread is computing for.
749         if (cd == mClientData) {
750
751             int total = mAllocations.size();
752
753             if (count == total) {
754                 // we're done: do something
755                 mDisplayModeCombo.setEnabled(true);
756                 mSaveButton.setEnabled(true);
757
758                 mStackCallThread = null;
759             } else {
760                 // work in progress, update the progress bar.
761 //                mUiThread.setStatusLine("Computing stack call: " + count
762 //                        + "/" + total);
763             }
764
765             // FIXME: attempt to only update when needed.
766             // Because the number of pages is not related to mAllocations.size() anymore
767             // due to pre-zygote/post-zygote display, update all the time.
768             // At some point we should remove the pages anyway, since it's getting computed
769             // really fast now.
770 //            if ((mCurrentPage + 1) * DISPLAY_PER_PAGE == count
771 //                    || (count == total && mCurrentPage == mPageCount - 1)) {
772             try {
773                 // get the current selection of the allocation
774                 int index = mAllocationTable.getSelectionIndex();
775                 NativeAllocationInfo info = null;
776
777                 if (index != -1) {
778                     info = (NativeAllocationInfo)mAllocationTable.getItem(index).getData();
779                 }
780
781                 // empty the table
782                 emptyTables();
783
784                 // fill it again
785                 fillAllocationTable();
786
787                 // reselect
788                 mAllocationTable.setSelection(index);
789
790                 // display detail table if needed
791                 if (info != null) {
792                     fillDetailTable(info);
793                 }
794             } catch (SWTException e) {
795                 if (mAllocationTable.isDisposed()) {
796                     // looks like the table is disposed. Let's ignore it.
797                 } else {
798                     throw e;
799                 }
800             }
801
802         } else {
803             // old client still running. doesn't really matter.
804         }
805     }
806
807     @Override
808     protected void setTableFocusListener() {
809         addTableToFocusListener(mAllocationTable);
810         addTableToFocusListener(mLibraryTable);
811         addTableToFocusListener(mLibraryAllocationTable);
812         addTableToFocusListener(mDetailTable);
813     }
814
815     protected void onAllocDisplayChange() {
816         mAllocDisplayMode = mAllocDisplayCombo.getSelectionIndex();
817
818         // create the new list
819         updateAllocDisplayList();
820
821         updateTotalMemoryDisplay();
822
823         // reset the ui.
824         mCurrentPage = 0;
825         updatePageUI();
826         switchDisplayMode();
827     }
828
829     private void updateAllocDisplayList() {
830         mTotalSize = 0;
831         mDisplayedAllocations.clear();
832         for (NativeAllocationInfo info : mAllocations) {
833             if (mAllocDisplayMode == ALLOC_DISPLAY_ALL ||
834                     (mAllocDisplayMode == ALLOC_DISPLAY_PRE_ZYGOTE ^ info.isZygoteChild())) {
835                 mDisplayedAllocations.add(info);
836                 mTotalSize += info.getSize() * info.getAllocationCount();
837             } else {
838                 // skip this item
839                 continue;
840             }
841         }
842
843         int count = mDisplayedAllocations.size();
844
845         mPageCount = count / DISPLAY_PER_PAGE;
846
847         // need to add a page for the rest of the div
848         if ((count % DISPLAY_PER_PAGE) > 0) {
849             mPageCount++;
850         }
851     }
852
853     private void updateTotalMemoryDisplay() {
854         switch (mAllocDisplayMode) {
855             case ALLOC_DISPLAY_ALL:
856                 mTotalMemoryLabel.setText(String.format("Total Memory: %1$s Bytes",
857                         sFormatter.format(mTotalSize)));
858                 break;
859             case ALLOC_DISPLAY_PRE_ZYGOTE:
860                 mTotalMemoryLabel.setText(String.format("Zygote Memory: %1$s Bytes",
861                         sFormatter.format(mTotalSize)));
862                 break;
863             case ALLOC_DISPLAY_POST_ZYGOTE:
864                 mTotalMemoryLabel.setText(String.format("Post-zygote Memory: %1$s Bytes",
865                         sFormatter.format(mTotalSize)));
866                 break;
867         }
868     }
869
870
871     private void switchDisplayMode() {
872         switch (mDisplayModeCombo.getSelectionIndex()) {
873             case 0: {// allocations
874                 mTopStackLayout.topControl = mTableModeControl;
875                 mAllocationStackLayout.topControl = mAllocationModeTop;
876                 mAllocationStackComposite.layout();
877                 mTopStackComposite.layout();
878                 emptyTables();
879                 fillAllocationTable();
880             }
881                 break;
882             case 1: {// libraries
883                 mTopStackLayout.topControl = mTableModeControl;
884                 mAllocationStackLayout.topControl = mLibraryModeTopControl;
885                 mAllocationStackComposite.layout();
886                 mTopStackComposite.layout();
887                 emptyTables();
888                 fillLibraryTable();
889             }
890                 break;
891         }
892     }
893
894     private void initAllocationDisplay() {
895         if (mStackCallThread != null) {
896             mStackCallThread.quit();
897         }
898
899         mAllocations.clear();
900         mAllocations.addAll(mClientData.getNativeAllocationList());
901
902         updateAllocDisplayList();
903
904         // if we have a previous clientdata and it matches the current one. we
905         // do a diff between the new list and the old one.
906         if (mBackUpClientData != null && mBackUpClientData == mClientData) {
907
908             ArrayList<NativeAllocationInfo> add = new ArrayList<NativeAllocationInfo>();
909
910             // we go through the list of NativeAllocationInfo in the new list and check if
911             // there's one with the same exact data (size, allocation, count and
912             // stackcall addresses) in the old list.
913             // if we don't find any, we add it to the "add" list
914             for (NativeAllocationInfo mi : mAllocations) {
915                 boolean found = false;
916                 for (NativeAllocationInfo old_mi : mBackUpAllocations) {
917                     if (mi.equals(old_mi)) {
918                         found = true;
919                         break;
920                     }
921                 }
922                 if (found == false) {
923                     add.add(mi);
924                 }
925             }
926
927             // put the result in mAllocations
928             mAllocations.clear();
929             mAllocations.addAll(add);
930
931             // display the difference in memory usage. This is computed
932             // calculating the memory usage of the objects in mAllocations.
933             int count = 0;
934             for (NativeAllocationInfo allocInfo : mAllocations) {
935                 count += allocInfo.getSize() * allocInfo.getAllocationCount();
936             }
937
938             mTotalMemoryLabel.setText(String.format("Memory Difference: %1$s Bytes",
939                     sFormatter.format(count)));
940         }
941         else {
942             // display the full memory usage
943             updateTotalMemoryDisplay();
944             //mDiffUpdateButton.setEnabled(mClientData.getTotalNativeMemory() > 0);
945         }
946         mTotalMemoryLabel.pack();
947
948         // update the page ui
949         mDisplayModeCombo.select(0);
950
951         mLibraryAllocations.clear();
952
953         // reset to first page
954         mCurrentPage = 0;
955
956         // update the label
957         updatePageUI();
958
959         // now fill the allocation Table with the current page
960         switchDisplayMode();
961
962         // start the thread to compute the stack calls
963         if (mAllocations.size() > 0) {
964             mStackCallThread = new StackCallThread(mClientData);
965             mStackCallThread.start();
966         }
967     }
968
969     private void updatePageUI() {
970
971         // set the label and pack to update the layout, otherwise
972         // the label will be cut off if the new size is bigger
973         if (mPageCount == 0) {
974             mPageLabel.setText("0 of 0 allocations.");
975         } else {
976             StringBuffer buffer = new StringBuffer();
977             // get our starting index
978             int start = (mCurrentPage * DISPLAY_PER_PAGE) + 1;
979             // end index, taking into account the last page can be half full
980             int count = mDisplayedAllocations.size();
981             int end = Math.min(start + DISPLAY_PER_PAGE - 1, count);
982             buffer.append(sFormatter.format(start));
983             buffer.append(" - ");
984             buffer.append(sFormatter.format(end));
985             buffer.append(" of ");
986             buffer.append(sFormatter.format(count));
987             buffer.append(" allocations.");
988             mPageLabel.setText(buffer.toString());
989         }
990
991         // handle the button enabled state.
992         mPagePreviousButton.setEnabled(mCurrentPage > 0);
993         // reminder: mCurrentPage starts at 0.
994         mPageNextButton.setEnabled(mCurrentPage < mPageCount - 1);
995
996         mPageLabel.pack();
997         mPageUIComposite.pack();
998
999     }
1000
1001     private void fillAllocationTable() {
1002         // get the count
1003         int count = mDisplayedAllocations.size();
1004
1005         // get our starting index
1006         int start = mCurrentPage * DISPLAY_PER_PAGE;
1007
1008         // loop for DISPLAY_PER_PAGE or till we reach count
1009         int end = start + DISPLAY_PER_PAGE;
1010
1011         for (int i = start; i < end && i < count; i++) {
1012             NativeAllocationInfo info = mDisplayedAllocations.get(i);
1013
1014             TableItem item = null;
1015
1016             if (mAllocDisplayMode == ALLOC_DISPLAY_ALL)  {
1017                 item = new TableItem(mAllocationTable, SWT.NONE);
1018                 item.setText(0, (info.isZygoteChild() ? "Z " : "") +
1019                         sFormatter.format(info.getSize() * info.getAllocationCount()));
1020                 item.setText(1, sFormatter.format(info.getAllocationCount()));
1021                 item.setText(2, sFormatter.format(info.getSize()));
1022             } else if (mAllocDisplayMode == ALLOC_DISPLAY_PRE_ZYGOTE ^ info.isZygoteChild()) {
1023                 item = new TableItem(mAllocationTable, SWT.NONE);
1024                 item.setText(0, sFormatter.format(info.getSize() * info.getAllocationCount()));
1025                 item.setText(1, sFormatter.format(info.getAllocationCount()));
1026                 item.setText(2, sFormatter.format(info.getSize()));
1027             } else {
1028                 // skip this item
1029                 continue;
1030             }
1031
1032             item.setData(info);
1033
1034             NativeStackCallInfo bti = info.getRelevantStackCallInfo();
1035             if (bti != null) {
1036                 String lib = bti.getLibraryName();
1037                 String method = bti.getMethodName();
1038                 String source = bti.getSourceFile();
1039                 if (lib != null)
1040                     item.setText(3, lib);
1041                 if (method != null)
1042                     item.setText(4, method);
1043                 if (source != null)
1044                     item.setText(5, source);
1045             }
1046         }
1047     }
1048
1049     private void fillLibraryTable() {
1050         // fill the library table
1051         sortAllocationsPerLibrary();
1052
1053         for (LibraryAllocations liballoc : mLibraryAllocations) {
1054             if (liballoc != null) {
1055                 TableItem item = new TableItem(mLibraryTable, SWT.NONE);
1056                 String lib = liballoc.getLibrary();
1057                 item.setText(0, lib != null ? lib : "");
1058                 item.setText(1, sFormatter.format(liballoc.getSize()));
1059                 item.setText(2, sFormatter.format(liballoc.getCount()));
1060             }
1061         }
1062     }
1063
1064     private void fillLibraryAllocationTable() {
1065         mLibraryAllocationTable.removeAll();
1066         mDetailTable.removeAll();
1067         int index = mLibraryTable.getSelectionIndex();
1068         if (index != -1) {
1069             LibraryAllocations liballoc = mLibraryAllocations.get(index);
1070             // start a thread that will fill table 10 at a time to keep the ui
1071             // responsive, but first we kill the previous one if there was one
1072             if (mFillTableThread != null) {
1073                 mFillTableThread.quit();
1074             }
1075             mFillTableThread = new FillTableThread(liballoc,
1076                     liballoc.getAllocationSize());
1077             mFillTableThread.start();
1078         }
1079     }
1080
1081     public void updateLibraryAllocationTable(LibraryAllocations liballoc,
1082             int start, int end) {
1083         try {
1084             if (mLibraryTable.isDisposed() == false) {
1085                 int index = mLibraryTable.getSelectionIndex();
1086                 if (index != -1) {
1087                     LibraryAllocations newliballoc = mLibraryAllocations.get(
1088                             index);
1089                     if (newliballoc == liballoc) {
1090                         int count = liballoc.getAllocationSize();
1091                         for (int i = start; i < end && i < count; i++) {
1092                             NativeAllocationInfo info = liballoc.getAllocation(i);
1093
1094                             TableItem item = new TableItem(
1095                                     mLibraryAllocationTable, SWT.NONE);
1096                             item.setText(0, sFormatter.format(
1097                                     info.getSize() * info.getAllocationCount()));
1098                             item.setText(1, sFormatter.format(info.getAllocationCount()));
1099                             item.setText(2, sFormatter.format(info.getSize()));
1100
1101                             NativeStackCallInfo stackCallInfo = info.getRelevantStackCallInfo();
1102                             if (stackCallInfo != null) {
1103                                 item.setText(3, stackCallInfo.getMethodName());
1104                             }
1105                         }
1106                     } else {
1107                         // we should quit the thread
1108                         if (mFillTableThread != null) {
1109                             mFillTableThread.quit();
1110                             mFillTableThread = null;
1111                         }
1112                     }
1113                 }
1114             }
1115         } catch (SWTException e) {
1116             Log.e("ddms", "error when updating the library allocation table");
1117         }
1118     }
1119
1120     private void fillDetailTable(final NativeAllocationInfo mi) {
1121         mDetailTable.removeAll();
1122         mDetailTable.setRedraw(false);
1123
1124         try {
1125             // populate the detail Table with the back trace
1126             Long[] addresses = mi.getStackCallAddresses();
1127             NativeStackCallInfo[] resolvedStackCall = mi.getResolvedStackCall();
1128
1129             if (resolvedStackCall == null) {
1130                 return;
1131             }
1132
1133             for (int i = 0 ; i < resolvedStackCall.length ; i++) {
1134                 if (addresses[i] == null || addresses[i].longValue() == 0) {
1135                     continue;
1136                 }
1137
1138                 long addr = addresses[i].longValue();
1139                 NativeStackCallInfo source = resolvedStackCall[i];
1140
1141                 TableItem item = new TableItem(mDetailTable, SWT.NONE);
1142                 item.setText(0, String.format("%08x", addr)); //$NON-NLS-1$
1143
1144                 String libraryName = source.getLibraryName();
1145                 String methodName = source.getMethodName();
1146                 String sourceFile = source.getSourceFile();
1147                 int lineNumber = source.getLineNumber();
1148
1149                 if (libraryName != null)
1150                     item.setText(1, libraryName);
1151                 if (methodName != null)
1152                     item.setText(2, methodName);
1153                 if (sourceFile != null)
1154                     item.setText(3, sourceFile);
1155                 if (lineNumber != -1)
1156                     item.setText(4, Integer.toString(lineNumber));
1157             }
1158         } finally {
1159             mDetailTable.setRedraw(true);
1160         }
1161     }
1162
1163     /*
1164      * Are updates enabled?
1165      */
1166     private void setUpdateStatus(int status) {
1167         switch (status) {
1168             case NOT_SELECTED:
1169                 mUpdateStatus.setText("Select a client to see heap info");
1170                 mAllocDisplayCombo.setEnabled(false);
1171                 mFullUpdateButton.setEnabled(false);
1172                 //mDiffUpdateButton.setEnabled(false);
1173                 break;
1174             case NOT_ENABLED:
1175                 mUpdateStatus.setText("Heap updates are " + "NOT ENABLED for this client");
1176                 mAllocDisplayCombo.setEnabled(false);
1177                 mFullUpdateButton.setEnabled(false);
1178                 //mDiffUpdateButton.setEnabled(false);
1179                 break;
1180             case ENABLED:
1181                 mUpdateStatus.setText("Press 'Full Update' to retrieve " + "latest data");
1182                 mAllocDisplayCombo.setEnabled(true);
1183                 mFullUpdateButton.setEnabled(true);
1184                 //mDiffUpdateButton.setEnabled(true);
1185                 break;
1186             default:
1187                 throw new RuntimeException();
1188         }
1189
1190         mUpdateStatus.pack();
1191     }
1192
1193     /**
1194      * Create the Table display. This includes a "detail" Table in the bottom
1195      * half and 2 modes in the top half: allocation Table and
1196      * library+allocations Tables.
1197      *
1198      * @param base the top parent to create the display into
1199      */
1200     private void createTableDisplay(Composite base) {
1201         final int minPanelWidth = 60;
1202
1203         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1204
1205         // top level composite for mode 1 & 2
1206         mTableModeControl = new Composite(base, SWT.NONE);
1207         GridLayout gl = new GridLayout(1, false);
1208         gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0;
1209         mTableModeControl.setLayout(gl);
1210         mTableModeControl.setLayoutData(new GridData(GridData.FILL_BOTH));
1211
1212         mTotalMemoryLabel = new Label(mTableModeControl, SWT.NONE);
1213         mTotalMemoryLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
1214         mTotalMemoryLabel.setText("Total Memory: 0 Bytes");
1215
1216         // the top half of these modes is dynamic
1217
1218         final Composite sash_composite = new Composite(mTableModeControl,
1219                 SWT.NONE);
1220         sash_composite.setLayout(new FormLayout());
1221         sash_composite.setLayoutData(new GridData(GridData.FILL_BOTH));
1222
1223         // create the stacked composite
1224         mAllocationStackComposite = new Composite(sash_composite, SWT.NONE);
1225         mAllocationStackLayout = new StackLayout();
1226         mAllocationStackComposite.setLayout(mAllocationStackLayout);
1227         mAllocationStackComposite.setLayoutData(new GridData(
1228                 GridData.FILL_BOTH));
1229
1230         // create the top half for mode 1
1231         createAllocationTopHalf(mAllocationStackComposite);
1232
1233         // create the top half for mode 2
1234         createLibraryTopHalf(mAllocationStackComposite);
1235
1236         final Sash sash = new Sash(sash_composite, SWT.HORIZONTAL);
1237
1238         // bottom half of these modes is the same: detail table
1239         createDetailTable(sash_composite);
1240
1241         // init value for stack
1242         mAllocationStackLayout.topControl = mAllocationModeTop;
1243
1244         // form layout data
1245         FormData data = new FormData();
1246         data.top = new FormAttachment(mTotalMemoryLabel, 0);
1247         data.bottom = new FormAttachment(sash, 0);
1248         data.left = new FormAttachment(0, 0);
1249         data.right = new FormAttachment(100, 0);
1250         mAllocationStackComposite.setLayoutData(data);
1251
1252         final FormData sashData = new FormData();
1253         if (prefs != null && prefs.contains(PREFS_ALLOCATION_SASH)) {
1254             sashData.top = new FormAttachment(0,
1255                     prefs.getInt(PREFS_ALLOCATION_SASH));
1256         } else {
1257             sashData.top = new FormAttachment(50, 0); // 50% across
1258         }
1259         sashData.left = new FormAttachment(0, 0);
1260         sashData.right = new FormAttachment(100, 0);
1261         sash.setLayoutData(sashData);
1262
1263         data = new FormData();
1264         data.top = new FormAttachment(sash, 0);
1265         data.bottom = new FormAttachment(100, 0);
1266         data.left = new FormAttachment(0, 0);
1267         data.right = new FormAttachment(100, 0);
1268         mDetailTable.setLayoutData(data);
1269
1270         // allow resizes, but cap at minPanelWidth
1271         sash.addListener(SWT.Selection, new Listener() {
1272             public void handleEvent(Event e) {
1273                 Rectangle sashRect = sash.getBounds();
1274                 Rectangle panelRect = sash_composite.getClientArea();
1275                 int bottom = panelRect.height - sashRect.height - minPanelWidth;
1276                 e.y = Math.max(Math.min(e.y, bottom), minPanelWidth);
1277                 if (e.y != sashRect.y) {
1278                     sashData.top = new FormAttachment(0, e.y);
1279                     prefs.setValue(PREFS_ALLOCATION_SASH, e.y);
1280                     sash_composite.layout();
1281                 }
1282             }
1283         });
1284     }
1285
1286     private void createDetailTable(Composite base) {
1287
1288         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1289
1290         mDetailTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION);
1291         mDetailTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1292         mDetailTable.setHeaderVisible(true);
1293         mDetailTable.setLinesVisible(true);
1294
1295         TableHelper.createTableColumn(mDetailTable, "Address", SWT.RIGHT,
1296                 "00000000", PREFS_DETAIL_ADDRESS, prefs); //$NON-NLS-1$
1297         TableHelper.createTableColumn(mDetailTable, "Library", SWT.LEFT,
1298                 "abcdefghijklmnopqrst", PREFS_DETAIL_LIBRARY, prefs); //$NON-NLS-1$
1299         TableHelper.createTableColumn(mDetailTable, "Method", SWT.LEFT,
1300                 "abcdefghijklmnopqrst", PREFS_DETAIL_METHOD, prefs); //$NON-NLS-1$
1301         TableHelper.createTableColumn(mDetailTable, "File", SWT.LEFT,
1302                 "abcdefghijklmnopqrstuvwxyz", PREFS_DETAIL_FILE, prefs); //$NON-NLS-1$
1303         TableHelper.createTableColumn(mDetailTable, "Line", SWT.RIGHT,
1304                 "9,999", PREFS_DETAIL_LINE, prefs); //$NON-NLS-1$
1305     }
1306
1307     private void createAllocationTopHalf(Composite b) {
1308         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1309
1310         Composite base = new Composite(b, SWT.NONE);
1311         mAllocationModeTop = base;
1312         GridLayout gl = new GridLayout(1, false);
1313         gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0;
1314         gl.verticalSpacing = 0;
1315         base.setLayout(gl);
1316         base.setLayoutData(new GridData(GridData.FILL_BOTH));
1317
1318         // horizontal layout for memory total and pages UI
1319         mPageUIComposite = new Composite(base, SWT.NONE);
1320         mPageUIComposite.setLayoutData(new GridData(
1321                 GridData.HORIZONTAL_ALIGN_BEGINNING));
1322         gl = new GridLayout(3, false);
1323         gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0;
1324         gl.horizontalSpacing = 0;
1325         mPageUIComposite.setLayout(gl);
1326
1327         // Page UI
1328         mPagePreviousButton = new Button(mPageUIComposite, SWT.NONE);
1329         mPagePreviousButton.setText("<");
1330         mPagePreviousButton.addSelectionListener(new SelectionAdapter() {
1331             @Override
1332             public void widgetSelected(SelectionEvent e) {
1333                 mCurrentPage--;
1334                 updatePageUI();
1335                 emptyTables();
1336                 fillAllocationTable();
1337             }
1338         });
1339
1340         mPageNextButton = new Button(mPageUIComposite, SWT.NONE);
1341         mPageNextButton.setText(">");
1342         mPageNextButton.addSelectionListener(new SelectionAdapter() {
1343             @Override
1344             public void widgetSelected(SelectionEvent e) {
1345                 mCurrentPage++;
1346                 updatePageUI();
1347                 emptyTables();
1348                 fillAllocationTable();
1349             }
1350         });
1351
1352         mPageLabel = new Label(mPageUIComposite, SWT.NONE);
1353         mPageLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
1354
1355         updatePageUI();
1356
1357         mAllocationTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION);
1358         mAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1359         mAllocationTable.setHeaderVisible(true);
1360         mAllocationTable.setLinesVisible(true);
1361
1362         TableHelper.createTableColumn(mAllocationTable, "Total", SWT.RIGHT,
1363                 "9,999,999", PREFS_ALLOC_TOTAL, prefs); //$NON-NLS-1$
1364         TableHelper.createTableColumn(mAllocationTable, "Count", SWT.RIGHT,
1365                 "9,999", PREFS_ALLOC_COUNT, prefs); //$NON-NLS-1$
1366         TableHelper.createTableColumn(mAllocationTable, "Size", SWT.RIGHT,
1367                 "999,999", PREFS_ALLOC_SIZE, prefs); //$NON-NLS-1$
1368         TableHelper.createTableColumn(mAllocationTable, "Library", SWT.LEFT,
1369                 "abcdefghijklmnopqrst", PREFS_ALLOC_LIBRARY, prefs); //$NON-NLS-1$
1370         TableHelper.createTableColumn(mAllocationTable, "Method", SWT.LEFT,
1371                 "abcdefghijklmnopqrst", PREFS_ALLOC_METHOD, prefs); //$NON-NLS-1$
1372         TableHelper.createTableColumn(mAllocationTable, "File", SWT.LEFT,
1373                 "abcdefghijklmnopqrstuvwxyz", PREFS_ALLOC_FILE, prefs); //$NON-NLS-1$
1374
1375         mAllocationTable.addSelectionListener(new SelectionAdapter() {
1376             @Override
1377             public void widgetSelected(SelectionEvent e) {
1378                 // get the selection index
1379                 int index = mAllocationTable.getSelectionIndex();
1380                 if (index >= 0 && index < mAllocationTable.getItemCount()) {
1381                     TableItem item = mAllocationTable.getItem(index);
1382                     if (item != null && item.getData() instanceof NativeAllocationInfo) {
1383                         fillDetailTable((NativeAllocationInfo)item.getData());
1384                     }
1385                 }
1386             }
1387         });
1388     }
1389
1390     private void createLibraryTopHalf(Composite base) {
1391         final int minPanelWidth = 60;
1392
1393         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1394
1395         // create a composite that'll contain 2 tables horizontally
1396         final Composite top = new Composite(base, SWT.NONE);
1397         mLibraryModeTopControl = top;
1398         top.setLayout(new FormLayout());
1399         top.setLayoutData(new GridData(GridData.FILL_BOTH));
1400
1401         // first table: library
1402         mLibraryTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION);
1403         mLibraryTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1404         mLibraryTable.setHeaderVisible(true);
1405         mLibraryTable.setLinesVisible(true);
1406
1407         TableHelper.createTableColumn(mLibraryTable, "Library", SWT.LEFT,
1408                 "abcdefghijklmnopqrstuvwxyz", PREFS_LIB_LIBRARY, prefs); //$NON-NLS-1$
1409         TableHelper.createTableColumn(mLibraryTable, "Size", SWT.RIGHT,
1410                 "9,999,999", PREFS_LIB_SIZE, prefs); //$NON-NLS-1$
1411         TableHelper.createTableColumn(mLibraryTable, "Count", SWT.RIGHT,
1412                 "9,999", PREFS_LIB_COUNT, prefs); //$NON-NLS-1$
1413
1414         mLibraryTable.addSelectionListener(new SelectionAdapter() {
1415             @Override
1416             public void widgetSelected(SelectionEvent e) {
1417                 fillLibraryAllocationTable();
1418             }
1419         });
1420
1421         final Sash sash = new Sash(top, SWT.VERTICAL);
1422
1423         // 2nd table: allocation per library
1424         mLibraryAllocationTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION);
1425         mLibraryAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1426         mLibraryAllocationTable.setHeaderVisible(true);
1427         mLibraryAllocationTable.setLinesVisible(true);
1428
1429         TableHelper.createTableColumn(mLibraryAllocationTable, "Total",
1430                 SWT.RIGHT, "9,999,999", PREFS_LIBALLOC_TOTAL, prefs); //$NON-NLS-1$
1431         TableHelper.createTableColumn(mLibraryAllocationTable, "Count",
1432                 SWT.RIGHT, "9,999", PREFS_LIBALLOC_COUNT, prefs); //$NON-NLS-1$
1433         TableHelper.createTableColumn(mLibraryAllocationTable, "Size",
1434                 SWT.RIGHT, "999,999", PREFS_LIBALLOC_SIZE, prefs); //$NON-NLS-1$
1435         TableHelper.createTableColumn(mLibraryAllocationTable, "Method",
1436                 SWT.LEFT, "abcdefghijklmnopqrst", PREFS_LIBALLOC_METHOD, prefs); //$NON-NLS-1$
1437
1438         mLibraryAllocationTable.addSelectionListener(new SelectionAdapter() {
1439             @Override
1440             public void widgetSelected(SelectionEvent e) {
1441                 // get the index of the selection in the library table
1442                 int index1 = mLibraryTable.getSelectionIndex();
1443                 // get the index in the library allocation table
1444                 int index2 = mLibraryAllocationTable.getSelectionIndex();
1445                 // get the MallocInfo object
1446                 if (index1 != -1 && index2 != -1) {
1447                     LibraryAllocations liballoc = mLibraryAllocations.get(index1);
1448                     NativeAllocationInfo info = liballoc.getAllocation(index2);
1449                     fillDetailTable(info);
1450                 }
1451             }
1452         });
1453
1454         // form layout data
1455         FormData data = new FormData();
1456         data.top = new FormAttachment(0, 0);
1457         data.bottom = new FormAttachment(100, 0);
1458         data.left = new FormAttachment(0, 0);
1459         data.right = new FormAttachment(sash, 0);
1460         mLibraryTable.setLayoutData(data);
1461
1462         final FormData sashData = new FormData();
1463         if (prefs != null && prefs.contains(PREFS_LIBRARY_SASH)) {
1464             sashData.left = new FormAttachment(0,
1465                     prefs.getInt(PREFS_LIBRARY_SASH));
1466         } else {
1467             sashData.left = new FormAttachment(50, 0);
1468         }
1469         sashData.bottom = new FormAttachment(100, 0);
1470         sashData.top = new FormAttachment(0, 0); // 50% across
1471         sash.setLayoutData(sashData);
1472
1473         data = new FormData();
1474         data.top = new FormAttachment(0, 0);
1475         data.bottom = new FormAttachment(100, 0);
1476         data.left = new FormAttachment(sash, 0);
1477         data.right = new FormAttachment(100, 0);
1478         mLibraryAllocationTable.setLayoutData(data);
1479
1480         // allow resizes, but cap at minPanelWidth
1481         sash.addListener(SWT.Selection, new Listener() {
1482             public void handleEvent(Event e) {
1483                 Rectangle sashRect = sash.getBounds();
1484                 Rectangle panelRect = top.getClientArea();
1485                 int right = panelRect.width - sashRect.width - minPanelWidth;
1486                 e.x = Math.max(Math.min(e.x, right), minPanelWidth);
1487                 if (e.x != sashRect.x) {
1488                     sashData.left = new FormAttachment(0, e.x);
1489                     prefs.setValue(PREFS_LIBRARY_SASH, e.y);
1490                     top.layout();
1491                 }
1492             }
1493         });
1494     }
1495
1496     private void emptyTables() {
1497         mAllocationTable.removeAll();
1498         mLibraryTable.removeAll();
1499         mLibraryAllocationTable.removeAll();
1500         mDetailTable.removeAll();
1501     }
1502
1503     private void sortAllocationsPerLibrary() {
1504         if (mClientData != null) {
1505             mLibraryAllocations.clear();
1506
1507             // create a hash map of LibraryAllocations to access aggregate
1508             // objects already created
1509             HashMap<String, LibraryAllocations> libcache =
1510                 new HashMap<String, LibraryAllocations>();
1511
1512             // get the allocation count
1513             int count = mDisplayedAllocations.size();
1514             for (int i = 0; i < count; i++) {
1515                 NativeAllocationInfo allocInfo = mDisplayedAllocations.get(i);
1516
1517                 NativeStackCallInfo stackCallInfo = allocInfo.getRelevantStackCallInfo();
1518                 if (stackCallInfo != null) {
1519                     String libraryName = stackCallInfo.getLibraryName();
1520                     LibraryAllocations liballoc = libcache.get(libraryName);
1521                     if (liballoc == null) {
1522                         // didn't find a library allocation object already
1523                         // created so we create one
1524                         liballoc = new LibraryAllocations(libraryName);
1525                         // add it to the cache
1526                         libcache.put(libraryName, liballoc);
1527                         // add it to the list
1528                         mLibraryAllocations.add(liballoc);
1529                     }
1530                     // add the MallocInfo object to it.
1531                     liballoc.addAllocation(allocInfo);
1532                 }
1533             }
1534             // now that the list is created, we need to compute the size and
1535             // sort it by size. This will also sort the MallocInfo objects
1536             // inside each LibraryAllocation objects.
1537             for (LibraryAllocations liballoc : mLibraryAllocations) {
1538                 liballoc.computeAllocationSizeAndCount();
1539             }
1540
1541             // now we sort it
1542             Collections.sort(mLibraryAllocations,
1543                     new Comparator<LibraryAllocations>() {
1544                 public int compare(LibraryAllocations o1,
1545                         LibraryAllocations o2) {
1546                     return o2.getSize() - o1.getSize();
1547                 }
1548             });
1549         }
1550     }
1551
1552     private void renderBitmap(ClientData cd) {
1553         byte[] pixData;
1554
1555         // Atomically get and clear the heap data.
1556         synchronized (cd) {
1557             if (serializeHeapData(cd.getVmHeapData()) == false) {
1558                 // no change, we return.
1559                 return;
1560             }
1561
1562             pixData = getSerializedData();
1563
1564             ImageData id = createLinearHeapImage(pixData, 200, mMapPalette);
1565             Image image = new Image(mBase.getDisplay(), id);
1566             mImage.setImage(image);
1567             mImage.pack(true);
1568         }
1569     }
1570
1571     /*
1572      * Create color palette for map.  Set up titles for legend.
1573      */
1574     private static PaletteData createPalette() {
1575         RGB colors[] = new RGB[NUM_PALETTE_ENTRIES];
1576         colors[0]
1577                 = new RGB(192, 192, 192); // non-heap pixels are gray
1578         mMapLegend[0]
1579                 = "(heap expansion area)";
1580
1581         colors[1]
1582                 = new RGB(0, 0, 0);       // free chunks are black
1583         mMapLegend[1]
1584                 = "free";
1585
1586         colors[HeapSegmentElement.KIND_OBJECT + 2]
1587                 = new RGB(0, 0, 255);     // objects are blue
1588         mMapLegend[HeapSegmentElement.KIND_OBJECT + 2]
1589                 = "data object";
1590
1591         colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
1592                 = new RGB(0, 255, 0);     // class objects are green
1593         mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
1594                 = "class object";
1595
1596         colors[HeapSegmentElement.KIND_ARRAY_1 + 2]
1597                 = new RGB(255, 0, 0);     // byte/bool arrays are red
1598         mMapLegend[HeapSegmentElement.KIND_ARRAY_1 + 2]
1599                 = "1-byte array (byte[], boolean[])";
1600
1601         colors[HeapSegmentElement.KIND_ARRAY_2 + 2]
1602                 = new RGB(255, 128, 0);   // short/char arrays are orange
1603         mMapLegend[HeapSegmentElement.KIND_ARRAY_2 + 2]
1604                 = "2-byte array (short[], char[])";
1605
1606         colors[HeapSegmentElement.KIND_ARRAY_4 + 2]
1607                 = new RGB(255, 255, 0);   // obj/int/float arrays are yellow
1608         mMapLegend[HeapSegmentElement.KIND_ARRAY_4 + 2]
1609                 = "4-byte array (object[], int[], float[])";
1610
1611         colors[HeapSegmentElement.KIND_ARRAY_8 + 2]
1612                 = new RGB(255, 128, 128); // long/double arrays are pink
1613         mMapLegend[HeapSegmentElement.KIND_ARRAY_8 + 2]
1614                 = "8-byte array (long[], double[])";
1615
1616         colors[HeapSegmentElement.KIND_UNKNOWN + 2]
1617                 = new RGB(255, 0, 255);   // unknown objects are cyan
1618         mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2]
1619                 = "unknown object";
1620
1621         colors[HeapSegmentElement.KIND_NATIVE + 2]
1622                 = new RGB(64, 64, 64);    // native objects are dark gray
1623         mMapLegend[HeapSegmentElement.KIND_NATIVE + 2]
1624                 = "non-Java object";
1625
1626         return new PaletteData(colors);
1627     }
1628
1629     private void saveAllocations(String fileName) {
1630         try {
1631             PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
1632
1633             for (NativeAllocationInfo alloc : mAllocations) {
1634                 out.println(alloc.toString());
1635             }
1636             out.close();
1637         } catch (IOException e) {
1638             Log.e("Native", e);
1639         }
1640     }
1641 }