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