OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / 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         mAllocations.clear();
896         mAllocations.addAll(mClientData.getNativeAllocationList());
897         
898         updateAllocDisplayList();
899
900         // if we have a previous clientdata and it matches the current one. we
901         // do a diff between the new list and the old one.
902         if (mBackUpClientData != null && mBackUpClientData == mClientData) {
903
904             ArrayList<NativeAllocationInfo> add = new ArrayList<NativeAllocationInfo>();
905
906             // we go through the list of NativeAllocationInfo in the new list and check if
907             // there's one with the same exact data (size, allocation, count and
908             // stackcall addresses) in the old list.
909             // if we don't find any, we add it to the "add" list
910             for (NativeAllocationInfo mi : mAllocations) {
911                 boolean found = false;
912                 for (NativeAllocationInfo old_mi : mBackUpAllocations) {
913                     if (mi.equals(old_mi)) {
914                         found = true;
915                         break;
916                     }
917                 }
918                 if (found == false) {
919                     add.add(mi);
920                 }
921             }
922
923             // put the result in mAllocations
924             mAllocations.clear();
925             mAllocations.addAll(add);
926
927             // display the difference in memory usage. This is computed
928             // calculating the memory usage of the objects in mAllocations.
929             int count = 0;
930             for (NativeAllocationInfo allocInfo : mAllocations) {
931                 count += allocInfo.getSize() * allocInfo.getAllocationCount();
932             }
933
934             mTotalMemoryLabel.setText(String.format("Memory Difference: %1$s Bytes",
935                     sFormatter.format(count)));
936         }
937         else {
938             // display the full memory usage
939             updateTotalMemoryDisplay();
940             //mDiffUpdateButton.setEnabled(mClientData.getTotalNativeMemory() > 0);
941         }
942         mTotalMemoryLabel.pack();
943
944         // update the page ui
945         mDisplayModeCombo.select(0);
946
947         mLibraryAllocations.clear();
948
949         // reset to first page
950         mCurrentPage = 0;
951
952         // update the label
953         updatePageUI();
954
955         // now fill the allocation Table with the current page
956         switchDisplayMode();
957
958         // start the thread to compute the stack calls
959         if (mAllocations.size() > 0) {
960             mStackCallThread = new StackCallThread(mClientData);
961             mStackCallThread.start();
962         }
963     }
964
965     private void updatePageUI() {
966
967         // set the label and pack to update the layout, otherwise
968         // the label will be cut off if the new size is bigger
969         if (mPageCount == 0) {
970             mPageLabel.setText("0 of 0 allocations.");
971         } else {
972             StringBuffer buffer = new StringBuffer();
973             // get our starting index
974             int start = (mCurrentPage * DISPLAY_PER_PAGE) + 1;
975             // end index, taking into account the last page can be half full
976             int count = mDisplayedAllocations.size();
977             int end = Math.min(start + DISPLAY_PER_PAGE - 1, count);
978             buffer.append(sFormatter.format(start));
979             buffer.append(" - ");
980             buffer.append(sFormatter.format(end));
981             buffer.append(" of ");
982             buffer.append(sFormatter.format(count));
983             buffer.append(" allocations.");
984             mPageLabel.setText(buffer.toString());
985         }
986
987         // handle the button enabled state.
988         mPagePreviousButton.setEnabled(mCurrentPage > 0);
989         // reminder: mCurrentPage starts at 0.
990         mPageNextButton.setEnabled(mCurrentPage < mPageCount - 1);
991
992         mPageLabel.pack();
993         mPageUIComposite.pack();
994
995     }
996
997     private void fillAllocationTable() {
998         // get the count
999         int count = mDisplayedAllocations.size();
1000
1001         // get our starting index
1002         int start = mCurrentPage * DISPLAY_PER_PAGE;
1003
1004         // loop for DISPLAY_PER_PAGE or till we reach count
1005         int end = start + DISPLAY_PER_PAGE;
1006
1007         for (int i = start; i < end && i < count; i++) {
1008             NativeAllocationInfo info = mDisplayedAllocations.get(i);
1009
1010             TableItem item = null;
1011
1012             if (mAllocDisplayMode == ALLOC_DISPLAY_ALL)  {
1013                 item = new TableItem(mAllocationTable, SWT.NONE);
1014                 item.setText(0, (info.isZygoteChild() ? "Z " : "") +
1015                         sFormatter.format(info.getSize() * info.getAllocationCount()));
1016                 item.setText(1, sFormatter.format(info.getAllocationCount()));
1017                 item.setText(2, sFormatter.format(info.getSize()));
1018             } else if (mAllocDisplayMode == ALLOC_DISPLAY_PRE_ZYGOTE ^ info.isZygoteChild()) {
1019                 item = new TableItem(mAllocationTable, SWT.NONE);
1020                 item.setText(0, sFormatter.format(info.getSize() * info.getAllocationCount()));
1021                 item.setText(1, sFormatter.format(info.getAllocationCount()));
1022                 item.setText(2, sFormatter.format(info.getSize()));
1023             } else {
1024                 // skip this item
1025                 continue;
1026             }
1027
1028             item.setData(info);
1029
1030             NativeStackCallInfo bti = info.getRelevantStackCallInfo();
1031             if (bti != null) {
1032                 String lib = bti.getLibraryName();
1033                 String method = bti.getMethodName();
1034                 String source = bti.getSourceFile();
1035                 if (lib != null)
1036                     item.setText(3, lib);
1037                 if (method != null)
1038                     item.setText(4, method);
1039                 if (source != null)
1040                     item.setText(5, source);
1041             }
1042         }
1043     }
1044
1045     private void fillLibraryTable() {
1046         // fill the library table
1047         sortAllocationsPerLibrary();
1048
1049         for (LibraryAllocations liballoc : mLibraryAllocations) {
1050             if (liballoc != null) {
1051                 TableItem item = new TableItem(mLibraryTable, SWT.NONE);
1052                 String lib = liballoc.getLibrary();
1053                 item.setText(0, lib != null ? lib : "");
1054                 item.setText(1, sFormatter.format(liballoc.getSize()));
1055                 item.setText(2, sFormatter.format(liballoc.getCount()));
1056             }
1057         }
1058     }
1059
1060     private void fillLibraryAllocationTable() {
1061         mLibraryAllocationTable.removeAll();
1062         mDetailTable.removeAll();
1063         int index = mLibraryTable.getSelectionIndex();
1064         if (index != -1) {
1065             LibraryAllocations liballoc = mLibraryAllocations.get(index);
1066             // start a thread that will fill table 10 at a time to keep the ui
1067             // responsive, but first we kill the previous one if there was one
1068             if (mFillTableThread != null) {
1069                 mFillTableThread.quit();
1070             }
1071             mFillTableThread = new FillTableThread(liballoc,
1072                     liballoc.getAllocationSize());
1073             mFillTableThread.start();
1074         }
1075     }
1076
1077     public void updateLibraryAllocationTable(LibraryAllocations liballoc,
1078             int start, int end) {
1079         try {
1080             if (mLibraryTable.isDisposed() == false) {
1081                 int index = mLibraryTable.getSelectionIndex();
1082                 if (index != -1) {
1083                     LibraryAllocations newliballoc = mLibraryAllocations.get(
1084                             index);
1085                     if (newliballoc == liballoc) {
1086                         int count = liballoc.getAllocationSize();
1087                         for (int i = start; i < end && i < count; i++) {
1088                             NativeAllocationInfo info = liballoc.getAllocation(i);
1089
1090                             TableItem item = new TableItem(
1091                                     mLibraryAllocationTable, SWT.NONE);
1092                             item.setText(0, sFormatter.format(
1093                                     info.getSize() * info.getAllocationCount()));
1094                             item.setText(1, sFormatter.format(info.getAllocationCount()));
1095                             item.setText(2, sFormatter.format(info.getSize()));
1096
1097                             NativeStackCallInfo stackCallInfo = info.getRelevantStackCallInfo();
1098                             if (stackCallInfo != null) {
1099                                 item.setText(3, stackCallInfo.getMethodName());
1100                             }
1101                         }
1102                     } else {
1103                         // we should quit the thread
1104                         if (mFillTableThread != null) {
1105                             mFillTableThread.quit();
1106                             mFillTableThread = null;
1107                         }
1108                     }
1109                 }
1110             }
1111         } catch (SWTException e) {
1112             Log.e("ddms", "error when updating the library allocation table");
1113         }
1114     }
1115
1116     private void fillDetailTable(final NativeAllocationInfo mi) {
1117         mDetailTable.removeAll();
1118         mDetailTable.setRedraw(false);
1119         
1120         try {
1121             // populate the detail Table with the back trace
1122             Long[] addresses = mi.getStackCallAddresses();
1123             NativeStackCallInfo[] resolvedStackCall = mi.getResolvedStackCall();
1124             
1125             if (resolvedStackCall == null) {
1126                 return;
1127             }
1128
1129             for (int i = 0 ; i < resolvedStackCall.length ; i++) {
1130                 if (addresses[i] == null || addresses[i].longValue() == 0) {
1131                     continue;
1132                 }
1133                 
1134                 long addr = addresses[i].longValue();
1135                 NativeStackCallInfo source = resolvedStackCall[i];
1136                 
1137                 TableItem item = new TableItem(mDetailTable, SWT.NONE);
1138                 item.setText(0, String.format("%08x", addr)); //$NON-NLS-1$
1139     
1140                 String libraryName = source.getLibraryName();
1141                 String methodName = source.getMethodName();
1142                 String sourceFile = source.getSourceFile();
1143                 int lineNumber = source.getLineNumber();
1144                 
1145                 if (libraryName != null)
1146                     item.setText(1, libraryName);
1147                 if (methodName != null)
1148                     item.setText(2, methodName);
1149                 if (sourceFile != null)
1150                     item.setText(3, sourceFile);
1151                 if (lineNumber != -1)
1152                     item.setText(4, Integer.toString(lineNumber));
1153             }
1154         } finally {
1155             mDetailTable.setRedraw(true);
1156         }
1157     }
1158
1159     /*
1160      * Are updates enabled?
1161      */
1162     private void setUpdateStatus(int status) {
1163         switch (status) {
1164             case NOT_SELECTED:
1165                 mUpdateStatus.setText("Select a client to see heap info");
1166                 mAllocDisplayCombo.setEnabled(false);
1167                 mFullUpdateButton.setEnabled(false);
1168                 //mDiffUpdateButton.setEnabled(false);
1169                 break;
1170             case NOT_ENABLED:
1171                 mUpdateStatus.setText("Heap updates are " + "NOT ENABLED for this client");
1172                 mAllocDisplayCombo.setEnabled(false);
1173                 mFullUpdateButton.setEnabled(false);
1174                 //mDiffUpdateButton.setEnabled(false);
1175                 break;
1176             case ENABLED:
1177                 mUpdateStatus.setText("Press 'Full Update' to retrieve " + "latest data");
1178                 mAllocDisplayCombo.setEnabled(true);
1179                 mFullUpdateButton.setEnabled(true);
1180                 //mDiffUpdateButton.setEnabled(true);
1181                 break;
1182             default:
1183                 throw new RuntimeException();
1184         }
1185
1186         mUpdateStatus.pack();
1187     }
1188
1189     /**
1190      * Create the Table display. This includes a "detail" Table in the bottom
1191      * half and 2 modes in the top half: allocation Table and
1192      * library+allocations Tables.
1193      *
1194      * @param base the top parent to create the display into
1195      */
1196     private void createTableDisplay(Composite base) {
1197         final int minPanelWidth = 60;
1198
1199         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1200
1201         // top level composite for mode 1 & 2
1202         mTableModeControl = new Composite(base, SWT.NONE);
1203         GridLayout gl = new GridLayout(1, false);
1204         gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0;
1205         mTableModeControl.setLayout(gl);
1206         mTableModeControl.setLayoutData(new GridData(GridData.FILL_BOTH));
1207
1208         mTotalMemoryLabel = new Label(mTableModeControl, SWT.NONE);
1209         mTotalMemoryLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
1210         mTotalMemoryLabel.setText("Total Memory: 0 Bytes");
1211
1212         // the top half of these modes is dynamic
1213
1214         final Composite sash_composite = new Composite(mTableModeControl,
1215                 SWT.NONE);
1216         sash_composite.setLayout(new FormLayout());
1217         sash_composite.setLayoutData(new GridData(GridData.FILL_BOTH));
1218
1219         // create the stacked composite
1220         mAllocationStackComposite = new Composite(sash_composite, SWT.NONE);
1221         mAllocationStackLayout = new StackLayout();
1222         mAllocationStackComposite.setLayout(mAllocationStackLayout);
1223         mAllocationStackComposite.setLayoutData(new GridData(
1224                 GridData.FILL_BOTH));
1225
1226         // create the top half for mode 1
1227         createAllocationTopHalf(mAllocationStackComposite);
1228
1229         // create the top half for mode 2
1230         createLibraryTopHalf(mAllocationStackComposite);
1231
1232         final Sash sash = new Sash(sash_composite, SWT.HORIZONTAL);
1233
1234         // bottom half of these modes is the same: detail table
1235         createDetailTable(sash_composite);
1236
1237         // init value for stack
1238         mAllocationStackLayout.topControl = mAllocationModeTop;
1239
1240         // form layout data
1241         FormData data = new FormData();
1242         data.top = new FormAttachment(mTotalMemoryLabel, 0);
1243         data.bottom = new FormAttachment(sash, 0);
1244         data.left = new FormAttachment(0, 0);
1245         data.right = new FormAttachment(100, 0);
1246         mAllocationStackComposite.setLayoutData(data);
1247
1248         final FormData sashData = new FormData();
1249         if (prefs != null && prefs.contains(PREFS_ALLOCATION_SASH)) {
1250             sashData.top = new FormAttachment(0,
1251                     prefs.getInt(PREFS_ALLOCATION_SASH));
1252         } else {
1253             sashData.top = new FormAttachment(50, 0); // 50% across
1254         }
1255         sashData.left = new FormAttachment(0, 0);
1256         sashData.right = new FormAttachment(100, 0);
1257         sash.setLayoutData(sashData);
1258
1259         data = new FormData();
1260         data.top = new FormAttachment(sash, 0);
1261         data.bottom = new FormAttachment(100, 0);
1262         data.left = new FormAttachment(0, 0);
1263         data.right = new FormAttachment(100, 0);
1264         mDetailTable.setLayoutData(data);
1265
1266         // allow resizes, but cap at minPanelWidth
1267         sash.addListener(SWT.Selection, new Listener() {
1268             public void handleEvent(Event e) {
1269                 Rectangle sashRect = sash.getBounds();
1270                 Rectangle panelRect = sash_composite.getClientArea();
1271                 int bottom = panelRect.height - sashRect.height - minPanelWidth;
1272                 e.y = Math.max(Math.min(e.y, bottom), minPanelWidth);
1273                 if (e.y != sashRect.y) {
1274                     sashData.top = new FormAttachment(0, e.y);
1275                     prefs.setValue(PREFS_ALLOCATION_SASH, e.y);
1276                     sash_composite.layout();
1277                 }
1278             }
1279         });
1280     }
1281
1282     private void createDetailTable(Composite base) {
1283
1284         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1285
1286         mDetailTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION);
1287         mDetailTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1288         mDetailTable.setHeaderVisible(true);
1289         mDetailTable.setLinesVisible(true);
1290
1291         TableHelper.createTableColumn(mDetailTable, "Address", SWT.RIGHT,
1292                 "00000000", PREFS_DETAIL_ADDRESS, prefs); //$NON-NLS-1$
1293         TableHelper.createTableColumn(mDetailTable, "Library", SWT.LEFT,
1294                 "abcdefghijklmnopqrst", PREFS_DETAIL_LIBRARY, prefs); //$NON-NLS-1$
1295         TableHelper.createTableColumn(mDetailTable, "Method", SWT.LEFT,
1296                 "abcdefghijklmnopqrst", PREFS_DETAIL_METHOD, prefs); //$NON-NLS-1$
1297         TableHelper.createTableColumn(mDetailTable, "File", SWT.LEFT,
1298                 "abcdefghijklmnopqrstuvwxyz", PREFS_DETAIL_FILE, prefs); //$NON-NLS-1$
1299         TableHelper.createTableColumn(mDetailTable, "Line", SWT.RIGHT,
1300                 "9,999", PREFS_DETAIL_LINE, prefs); //$NON-NLS-1$
1301     }
1302
1303     private void createAllocationTopHalf(Composite b) {
1304         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1305
1306         Composite base = new Composite(b, SWT.NONE);
1307         mAllocationModeTop = base;
1308         GridLayout gl = new GridLayout(1, false);
1309         gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0;
1310         gl.verticalSpacing = 0;
1311         base.setLayout(gl);
1312         base.setLayoutData(new GridData(GridData.FILL_BOTH));
1313
1314         // horizontal layout for memory total and pages UI
1315         mPageUIComposite = new Composite(base, SWT.NONE);
1316         mPageUIComposite.setLayoutData(new GridData(
1317                 GridData.HORIZONTAL_ALIGN_BEGINNING));
1318         gl = new GridLayout(3, false);
1319         gl.marginLeft = gl.marginRight = gl.marginTop = gl.marginBottom = 0;
1320         gl.horizontalSpacing = 0;
1321         mPageUIComposite.setLayout(gl);
1322
1323         // Page UI
1324         mPagePreviousButton = new Button(mPageUIComposite, SWT.NONE);
1325         mPagePreviousButton.setText("<");
1326         mPagePreviousButton.addSelectionListener(new SelectionAdapter() {
1327             @Override
1328             public void widgetSelected(SelectionEvent e) {
1329                 mCurrentPage--;
1330                 updatePageUI();
1331                 emptyTables();
1332                 fillAllocationTable();
1333             }
1334         });
1335
1336         mPageNextButton = new Button(mPageUIComposite, SWT.NONE);
1337         mPageNextButton.setText(">");
1338         mPageNextButton.addSelectionListener(new SelectionAdapter() {
1339             @Override
1340             public void widgetSelected(SelectionEvent e) {
1341                 mCurrentPage++;
1342                 updatePageUI();
1343                 emptyTables();
1344                 fillAllocationTable();
1345             }
1346         });
1347
1348         mPageLabel = new Label(mPageUIComposite, SWT.NONE);
1349         mPageLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
1350
1351         updatePageUI();
1352
1353         mAllocationTable = new Table(base, SWT.MULTI | SWT.FULL_SELECTION);
1354         mAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1355         mAllocationTable.setHeaderVisible(true);
1356         mAllocationTable.setLinesVisible(true);
1357
1358         TableHelper.createTableColumn(mAllocationTable, "Total", SWT.RIGHT,
1359                 "9,999,999", PREFS_ALLOC_TOTAL, prefs); //$NON-NLS-1$
1360         TableHelper.createTableColumn(mAllocationTable, "Count", SWT.RIGHT,
1361                 "9,999", PREFS_ALLOC_COUNT, prefs); //$NON-NLS-1$
1362         TableHelper.createTableColumn(mAllocationTable, "Size", SWT.RIGHT,
1363                 "999,999", PREFS_ALLOC_SIZE, prefs); //$NON-NLS-1$
1364         TableHelper.createTableColumn(mAllocationTable, "Library", SWT.LEFT,
1365                 "abcdefghijklmnopqrst", PREFS_ALLOC_LIBRARY, prefs); //$NON-NLS-1$
1366         TableHelper.createTableColumn(mAllocationTable, "Method", SWT.LEFT,
1367                 "abcdefghijklmnopqrst", PREFS_ALLOC_METHOD, prefs); //$NON-NLS-1$
1368         TableHelper.createTableColumn(mAllocationTable, "File", SWT.LEFT,
1369                 "abcdefghijklmnopqrstuvwxyz", PREFS_ALLOC_FILE, prefs); //$NON-NLS-1$
1370
1371         mAllocationTable.addSelectionListener(new SelectionAdapter() {
1372             @Override
1373             public void widgetSelected(SelectionEvent e) {
1374                 // get the selection index
1375                 int index = mAllocationTable.getSelectionIndex();
1376                 TableItem item = mAllocationTable.getItem(index);
1377                 if (item != null && item.getData() instanceof NativeAllocationInfo) {
1378                     fillDetailTable((NativeAllocationInfo)item.getData());
1379                 }
1380             }
1381         });
1382     }
1383
1384     private void createLibraryTopHalf(Composite base) {
1385         final int minPanelWidth = 60;
1386
1387         final IPreferenceStore prefs = DdmUiPreferences.getStore();
1388
1389         // create a composite that'll contain 2 tables horizontally
1390         final Composite top = new Composite(base, SWT.NONE);
1391         mLibraryModeTopControl = top;
1392         top.setLayout(new FormLayout());
1393         top.setLayoutData(new GridData(GridData.FILL_BOTH));
1394
1395         // first table: library
1396         mLibraryTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION);
1397         mLibraryTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1398         mLibraryTable.setHeaderVisible(true);
1399         mLibraryTable.setLinesVisible(true);
1400
1401         TableHelper.createTableColumn(mLibraryTable, "Library", SWT.LEFT,
1402                 "abcdefghijklmnopqrstuvwxyz", PREFS_LIB_LIBRARY, prefs); //$NON-NLS-1$
1403         TableHelper.createTableColumn(mLibraryTable, "Size", SWT.RIGHT,
1404                 "9,999,999", PREFS_LIB_SIZE, prefs); //$NON-NLS-1$
1405         TableHelper.createTableColumn(mLibraryTable, "Count", SWT.RIGHT,
1406                 "9,999", PREFS_LIB_COUNT, prefs); //$NON-NLS-1$
1407
1408         mLibraryTable.addSelectionListener(new SelectionAdapter() {
1409             @Override
1410             public void widgetSelected(SelectionEvent e) {
1411                 fillLibraryAllocationTable();
1412             }
1413         });
1414
1415         final Sash sash = new Sash(top, SWT.VERTICAL);
1416
1417         // 2nd table: allocation per library
1418         mLibraryAllocationTable = new Table(top, SWT.MULTI | SWT.FULL_SELECTION);
1419         mLibraryAllocationTable.setLayoutData(new GridData(GridData.FILL_BOTH));
1420         mLibraryAllocationTable.setHeaderVisible(true);
1421         mLibraryAllocationTable.setLinesVisible(true);
1422
1423         TableHelper.createTableColumn(mLibraryAllocationTable, "Total",
1424                 SWT.RIGHT, "9,999,999", PREFS_LIBALLOC_TOTAL, prefs); //$NON-NLS-1$
1425         TableHelper.createTableColumn(mLibraryAllocationTable, "Count",
1426                 SWT.RIGHT, "9,999", PREFS_LIBALLOC_COUNT, prefs); //$NON-NLS-1$
1427         TableHelper.createTableColumn(mLibraryAllocationTable, "Size",
1428                 SWT.RIGHT, "999,999", PREFS_LIBALLOC_SIZE, prefs); //$NON-NLS-1$
1429         TableHelper.createTableColumn(mLibraryAllocationTable, "Method",
1430                 SWT.LEFT, "abcdefghijklmnopqrst", PREFS_LIBALLOC_METHOD, prefs); //$NON-NLS-1$
1431
1432         mLibraryAllocationTable.addSelectionListener(new SelectionAdapter() {
1433             @Override
1434             public void widgetSelected(SelectionEvent e) {
1435                 // get the index of the selection in the library table
1436                 int index1 = mLibraryTable.getSelectionIndex();
1437                 // get the index in the library allocation table
1438                 int index2 = mLibraryAllocationTable.getSelectionIndex();
1439                 // get the MallocInfo object
1440                 LibraryAllocations liballoc = mLibraryAllocations.get(index1);
1441                 NativeAllocationInfo info = liballoc.getAllocation(index2);
1442                 fillDetailTable(info);
1443             }
1444         });
1445
1446         // form layout data
1447         FormData data = new FormData();
1448         data.top = new FormAttachment(0, 0);
1449         data.bottom = new FormAttachment(100, 0);
1450         data.left = new FormAttachment(0, 0);
1451         data.right = new FormAttachment(sash, 0);
1452         mLibraryTable.setLayoutData(data);
1453
1454         final FormData sashData = new FormData();
1455         if (prefs != null && prefs.contains(PREFS_LIBRARY_SASH)) {
1456             sashData.left = new FormAttachment(0,
1457                     prefs.getInt(PREFS_LIBRARY_SASH));
1458         } else {
1459             sashData.left = new FormAttachment(50, 0);
1460         }
1461         sashData.bottom = new FormAttachment(100, 0);
1462         sashData.top = new FormAttachment(0, 0); // 50% across
1463         sash.setLayoutData(sashData);
1464
1465         data = new FormData();
1466         data.top = new FormAttachment(0, 0);
1467         data.bottom = new FormAttachment(100, 0);
1468         data.left = new FormAttachment(sash, 0);
1469         data.right = new FormAttachment(100, 0);
1470         mLibraryAllocationTable.setLayoutData(data);
1471
1472         // allow resizes, but cap at minPanelWidth
1473         sash.addListener(SWT.Selection, new Listener() {
1474             public void handleEvent(Event e) {
1475                 Rectangle sashRect = sash.getBounds();
1476                 Rectangle panelRect = top.getClientArea();
1477                 int right = panelRect.width - sashRect.width - minPanelWidth;
1478                 e.x = Math.max(Math.min(e.x, right), minPanelWidth);
1479                 if (e.x != sashRect.x) {
1480                     sashData.left = new FormAttachment(0, e.x);
1481                     prefs.setValue(PREFS_LIBRARY_SASH, e.y);
1482                     top.layout();
1483                 }
1484             }
1485         });
1486     }
1487
1488     private void emptyTables() {
1489         mAllocationTable.removeAll();
1490         mLibraryTable.removeAll();
1491         mLibraryAllocationTable.removeAll();
1492         mDetailTable.removeAll();
1493     }
1494
1495     private void sortAllocationsPerLibrary() {
1496         if (mClientData != null) {
1497             mLibraryAllocations.clear();
1498             
1499             // create a hash map of LibraryAllocations to access aggregate
1500             // objects already created
1501             HashMap<String, LibraryAllocations> libcache =
1502                 new HashMap<String, LibraryAllocations>();
1503
1504             // get the allocation count
1505             int count = mDisplayedAllocations.size();
1506             for (int i = 0; i < count; i++) {
1507                 NativeAllocationInfo allocInfo = mDisplayedAllocations.get(i);
1508
1509                 NativeStackCallInfo stackCallInfo = allocInfo.getRelevantStackCallInfo();
1510                 if (stackCallInfo != null) {
1511                     String libraryName = stackCallInfo.getLibraryName();
1512                     LibraryAllocations liballoc = libcache.get(libraryName);
1513                     if (liballoc == null) {
1514                         // didn't find a library allocation object already
1515                         // created so we create one
1516                         liballoc = new LibraryAllocations(libraryName);
1517                         // add it to the cache
1518                         libcache.put(libraryName, liballoc);
1519                         // add it to the list
1520                         mLibraryAllocations.add(liballoc);
1521                     }
1522                     // add the MallocInfo object to it.
1523                     liballoc.addAllocation(allocInfo);
1524                 }
1525             }
1526             // now that the list is created, we need to compute the size and
1527             // sort it by size. This will also sort the MallocInfo objects
1528             // inside each LibraryAllocation objects.
1529             for (LibraryAllocations liballoc : mLibraryAllocations) {
1530                 liballoc.computeAllocationSizeAndCount();
1531             }
1532
1533             // now we sort it
1534             Collections.sort(mLibraryAllocations,
1535                     new Comparator<LibraryAllocations>() {
1536                 public int compare(LibraryAllocations o1,
1537                         LibraryAllocations o2) {
1538                     return o2.getSize() - o1.getSize();
1539                 }
1540             });
1541         }
1542     }
1543
1544     private void renderBitmap(ClientData cd) {
1545         byte[] pixData;
1546
1547         // Atomically get and clear the heap data.
1548         synchronized (cd) {
1549             if (serializeHeapData(cd.getVmHeapData()) == false) {
1550                 // no change, we return.
1551                 return;
1552             }
1553
1554             pixData = getSerializedData();
1555
1556             ImageData id = createLinearHeapImage(pixData, 200, mMapPalette);
1557             Image image = new Image(mBase.getDisplay(), id);
1558             mImage.setImage(image);
1559             mImage.pack(true);
1560         }
1561     }
1562
1563     /*
1564      * Create color palette for map.  Set up titles for legend.
1565      */
1566     private static PaletteData createPalette() {
1567         RGB colors[] = new RGB[NUM_PALETTE_ENTRIES];
1568         colors[0]
1569                 = new RGB(192, 192, 192); // non-heap pixels are gray
1570         mMapLegend[0]
1571                 = "(heap expansion area)";
1572
1573         colors[1]
1574                 = new RGB(0, 0, 0);       // free chunks are black
1575         mMapLegend[1]
1576                 = "free";
1577
1578         colors[HeapSegmentElement.KIND_OBJECT + 2]
1579                 = new RGB(0, 0, 255);     // objects are blue
1580         mMapLegend[HeapSegmentElement.KIND_OBJECT + 2]
1581                 = "data object";
1582
1583         colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
1584                 = new RGB(0, 255, 0);     // class objects are green
1585         mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
1586                 = "class object";
1587
1588         colors[HeapSegmentElement.KIND_ARRAY_1 + 2]
1589                 = new RGB(255, 0, 0);     // byte/bool arrays are red
1590         mMapLegend[HeapSegmentElement.KIND_ARRAY_1 + 2]
1591                 = "1-byte array (byte[], boolean[])";
1592
1593         colors[HeapSegmentElement.KIND_ARRAY_2 + 2]
1594                 = new RGB(255, 128, 0);   // short/char arrays are orange
1595         mMapLegend[HeapSegmentElement.KIND_ARRAY_2 + 2]
1596                 = "2-byte array (short[], char[])";
1597
1598         colors[HeapSegmentElement.KIND_ARRAY_4 + 2]
1599                 = new RGB(255, 255, 0);   // obj/int/float arrays are yellow
1600         mMapLegend[HeapSegmentElement.KIND_ARRAY_4 + 2]
1601                 = "4-byte array (object[], int[], float[])";
1602
1603         colors[HeapSegmentElement.KIND_ARRAY_8 + 2]
1604                 = new RGB(255, 128, 128); // long/double arrays are pink
1605         mMapLegend[HeapSegmentElement.KIND_ARRAY_8 + 2]
1606                 = "8-byte array (long[], double[])";
1607
1608         colors[HeapSegmentElement.KIND_UNKNOWN + 2]
1609                 = new RGB(255, 0, 255);   // unknown objects are cyan
1610         mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2]
1611                 = "unknown object";
1612
1613         colors[HeapSegmentElement.KIND_NATIVE + 2]
1614                 = new RGB(64, 64, 64);    // native objects are dark gray
1615         mMapLegend[HeapSegmentElement.KIND_NATIVE + 2]
1616                 = "non-Java object";
1617
1618         return new PaletteData(colors);
1619     }
1620     
1621     private void saveAllocations(String fileName) {
1622         try {
1623             PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
1624     
1625             for (NativeAllocationInfo alloc : mAllocations) {
1626                 out.println(alloc.toString());
1627             }
1628             out.close();
1629         } catch (IOException e) {
1630             Log.e("Native", e);
1631         }
1632     }
1633 }