• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.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.AndroidDebugBridge.IClientChangeListener;
23 import com.android.ddmlib.HeapSegment.HeapSegmentElement;
24 
25 import org.eclipse.jface.preference.IPreferenceStore;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.SWTException;
28 import org.eclipse.swt.custom.StackLayout;
29 import org.eclipse.swt.events.SelectionAdapter;
30 import org.eclipse.swt.events.SelectionEvent;
31 import org.eclipse.swt.graphics.Color;
32 import org.eclipse.swt.graphics.Font;
33 import org.eclipse.swt.graphics.FontData;
34 import org.eclipse.swt.graphics.GC;
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.Point;
39 import org.eclipse.swt.graphics.RGB;
40 import org.eclipse.swt.layout.GridData;
41 import org.eclipse.swt.layout.GridLayout;
42 import org.eclipse.swt.widgets.Button;
43 import org.eclipse.swt.widgets.Combo;
44 import org.eclipse.swt.widgets.Composite;
45 import org.eclipse.swt.widgets.Control;
46 import org.eclipse.swt.widgets.Display;
47 import org.eclipse.swt.widgets.Group;
48 import org.eclipse.swt.widgets.Label;
49 import org.eclipse.swt.widgets.Table;
50 import org.eclipse.swt.widgets.TableColumn;
51 import org.eclipse.swt.widgets.TableItem;
52 import org.jfree.chart.ChartFactory;
53 import org.jfree.chart.JFreeChart;
54 import org.jfree.chart.axis.CategoryAxis;
55 import org.jfree.chart.axis.CategoryLabelPositions;
56 import org.jfree.chart.labels.CategoryToolTipGenerator;
57 import org.jfree.chart.plot.CategoryPlot;
58 import org.jfree.chart.plot.Plot;
59 import org.jfree.chart.plot.PlotOrientation;
60 import org.jfree.chart.renderer.category.CategoryItemRenderer;
61 import org.jfree.chart.title.TextTitle;
62 import org.jfree.data.category.CategoryDataset;
63 import org.jfree.data.category.DefaultCategoryDataset;
64 import org.jfree.experimental.chart.swt.ChartComposite;
65 import org.jfree.experimental.swt.SWTUtils;
66 
67 import java.io.ByteArrayInputStream;
68 import java.io.IOException;
69 import java.io.InputStream;
70 import java.text.NumberFormat;
71 import java.util.ArrayList;
72 import java.util.Iterator;
73 import java.util.Map;
74 import java.util.Set;
75 
76 
77 /**
78  * Base class for our information panels.
79  */
80 public final class HeapPanel extends BaseHeapPanel {
81     private static final String PREFS_STATS_COL_TYPE = "heapPanel.col0"; //$NON-NLS-1$
82     private static final String PREFS_STATS_COL_COUNT = "heapPanel.col1"; //$NON-NLS-1$
83     private static final String PREFS_STATS_COL_SIZE = "heapPanel.col2"; //$NON-NLS-1$
84     private static final String PREFS_STATS_COL_SMALLEST = "heapPanel.col3"; //$NON-NLS-1$
85     private static final String PREFS_STATS_COL_LARGEST = "heapPanel.col4"; //$NON-NLS-1$
86     private static final String PREFS_STATS_COL_MEDIAN = "heapPanel.col5"; //$NON-NLS-1$
87     private static final String PREFS_STATS_COL_AVERAGE = "heapPanel.col6"; //$NON-NLS-1$
88 
89     /* args to setUpdateStatus() */
90     private static final int NOT_SELECTED   = 0;
91     private static final int NOT_ENABLED    = 1;
92     private static final int ENABLED        = 2;
93 
94     /** color palette and map legend. NATIVE is the last enum is a 0 based enum list, so we need
95      * Native+1 at least. We also need 2 more entries for free area and expansion area.  */
96     private static final int NUM_PALETTE_ENTRIES = HeapSegmentElement.KIND_NATIVE+2 +1;
97     private static final String[] mMapLegend = new String[NUM_PALETTE_ENTRIES];
98     private static final PaletteData mMapPalette = createPalette();
99 
100     private static final boolean DISPLAY_HEAP_BITMAP = false;
101     private static final boolean DISPLAY_HILBERT_BITMAP = false;
102 
103     private static final int PLACEHOLDER_HILBERT_SIZE = 200;
104     private static final int PLACEHOLDER_LINEAR_V_SIZE = 100;
105     private static final int PLACEHOLDER_LINEAR_H_SIZE = 300;
106 
107     private static final int[] ZOOMS = {100, 50, 25};
108 
109     private static final NumberFormat sByteFormatter = NumberFormat.getInstance();
110     private static final NumberFormat sLargeByteFormatter = NumberFormat.getInstance();
111     private static final NumberFormat sCountFormatter = NumberFormat.getInstance();
112 
113     static {
114         sByteFormatter.setMinimumFractionDigits(0);
115         sByteFormatter.setMaximumFractionDigits(1);
116         sLargeByteFormatter.setMinimumFractionDigits(3);
117         sLargeByteFormatter.setMaximumFractionDigits(3);
118 
119         sCountFormatter.setGroupingUsed(true);
120     }
121 
122     private Display mDisplay;
123 
124     private Composite mTop; // real top
125     private Label mUpdateStatus;
126     private Table mHeapSummary;
127     private Combo mDisplayMode;
128 
129     //private ScrolledComposite mScrolledComposite;
130 
131     private Composite mDisplayBase; // base of the displays.
132     private StackLayout mDisplayStack;
133 
134     private Composite mStatisticsBase;
135     private Table mStatisticsTable;
136     private JFreeChart mChart;
137     private ChartComposite mChartComposite;
138     private Button mGcButton;
139     private DefaultCategoryDataset mAllocCountDataSet;
140 
141     private Composite mLinearBase;
142     private Label mLinearHeapImage;
143 
144     private Composite mHilbertBase;
145     private Label mHilbertHeapImage;
146     private Group mLegend;
147     private Combo mZoom;
148 
149     /** Image used for the hilbert display. Since we recreate a new image every time, we
150      * keep this one around to dispose it. */
151     private Image mHilbertImage;
152     private Image mLinearImage;
153     private Composite[] mLayout;
154 
155     /*
156      * Create color palette for map.  Set up titles for legend.
157      */
createPalette()158     private static PaletteData createPalette() {
159         RGB colors[] = new RGB[NUM_PALETTE_ENTRIES];
160         colors[0]
161                 = new RGB(192, 192, 192); // non-heap pixels are gray
162         mMapLegend[0]
163                 = "(heap expansion area)";
164 
165         colors[1]
166                 = new RGB(0, 0, 0);       // free chunks are black
167         mMapLegend[1]
168                 = "free";
169 
170         colors[HeapSegmentElement.KIND_OBJECT + 2]
171                 = new RGB(0, 0, 255);     // objects are blue
172         mMapLegend[HeapSegmentElement.KIND_OBJECT + 2]
173                 = "data object";
174 
175         colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
176                 = new RGB(0, 255, 0);     // class objects are green
177         mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
178                 = "class object";
179 
180         colors[HeapSegmentElement.KIND_ARRAY_1 + 2]
181                 = new RGB(255, 0, 0);     // byte/bool arrays are red
182         mMapLegend[HeapSegmentElement.KIND_ARRAY_1 + 2]
183                 = "1-byte array (byte[], boolean[])";
184 
185         colors[HeapSegmentElement.KIND_ARRAY_2 + 2]
186                 = new RGB(255, 128, 0);   // short/char arrays are orange
187         mMapLegend[HeapSegmentElement.KIND_ARRAY_2 + 2]
188                 = "2-byte array (short[], char[])";
189 
190         colors[HeapSegmentElement.KIND_ARRAY_4 + 2]
191                 = new RGB(255, 255, 0);   // obj/int/float arrays are yellow
192         mMapLegend[HeapSegmentElement.KIND_ARRAY_4 + 2]
193                 = "4-byte array (object[], int[], float[])";
194 
195         colors[HeapSegmentElement.KIND_ARRAY_8 + 2]
196                 = new RGB(255, 128, 128); // long/double arrays are pink
197         mMapLegend[HeapSegmentElement.KIND_ARRAY_8 + 2]
198                 = "8-byte array (long[], double[])";
199 
200         colors[HeapSegmentElement.KIND_UNKNOWN + 2]
201                 = new RGB(255, 0, 255);   // unknown objects are cyan
202         mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2]
203                 = "unknown object";
204 
205         colors[HeapSegmentElement.KIND_NATIVE + 2]
206                 = new RGB(64, 64, 64);    // native objects are dark gray
207         mMapLegend[HeapSegmentElement.KIND_NATIVE + 2]
208                 = "non-Java object";
209 
210         return new PaletteData(colors);
211     }
212 
213     /**
214      * Sent when an existing client information changed.
215      * <p/>
216      * This is sent from a non UI thread.
217      * @param client the updated client.
218      * @param changeMask the bit mask describing the changed properties. It can contain
219      * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
220      * {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
221      * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
222      * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
223      *
224      * @see IClientChangeListener#clientChanged(Client, int)
225      */
clientChanged(final Client client, int changeMask)226     public void clientChanged(final Client client, int changeMask) {
227         if (client == getCurrentClient()) {
228             if ((changeMask & Client.CHANGE_HEAP_MODE) == Client.CHANGE_HEAP_MODE ||
229                     (changeMask & Client.CHANGE_HEAP_DATA) == Client.CHANGE_HEAP_DATA) {
230                 try {
231                     mTop.getDisplay().asyncExec(new Runnable() {
232                         public void run() {
233                             clientSelected();
234                         }
235                     });
236                 } catch (SWTException e) {
237                     // display is disposed (app is quitting most likely), we do nothing.
238                 }
239             }
240         }
241     }
242 
243     /**
244      * Sent when a new device is selected. The new device can be accessed
245      * with {@link #getCurrentDevice()}
246      */
247     @Override
deviceSelected()248     public void deviceSelected() {
249         // pass
250     }
251 
252     /**
253      * Sent when a new client is selected. The new client can be accessed
254      * with {@link #getCurrentClient()}.
255      */
256     @Override
clientSelected()257     public void clientSelected() {
258         if (mTop.isDisposed())
259             return;
260 
261         Client client = getCurrentClient();
262 
263         Log.d("ddms", "HeapPanel: changed " + client);
264 
265         if (client != null) {
266             ClientData cd = client.getClientData();
267 
268             if (client.isHeapUpdateEnabled()) {
269                 mGcButton.setEnabled(true);
270                 mDisplayMode.setEnabled(true);
271                 setUpdateStatus(ENABLED);
272             } else {
273                 setUpdateStatus(NOT_ENABLED);
274                 mGcButton.setEnabled(false);
275                 mDisplayMode.setEnabled(false);
276             }
277 
278             fillSummaryTable(cd);
279 
280             int mode = mDisplayMode.getSelectionIndex();
281             if (mode == 0) {
282                 fillDetailedTable(client, false /* forceRedraw */);
283             } else {
284                 if (DISPLAY_HEAP_BITMAP) {
285                     renderHeapData(cd, mode - 1, false /* forceRedraw */);
286                 }
287             }
288         } else {
289             mGcButton.setEnabled(false);
290             mDisplayMode.setEnabled(false);
291             fillSummaryTable(null);
292             fillDetailedTable(null, true);
293             setUpdateStatus(NOT_SELECTED);
294         }
295 
296         // sizes of things change frequently, so redo layout
297         //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
298         //        SWT.DEFAULT));
299         mDisplayBase.layout();
300         //mScrolledComposite.redraw();
301     }
302 
303     /**
304      * Create our control(s).
305      */
306     @Override
createControl(Composite parent)307     protected Control createControl(Composite parent) {
308         mDisplay = parent.getDisplay();
309 
310         GridLayout gl;
311 
312         mTop = new Composite(parent, SWT.NONE);
313         mTop.setLayout(new GridLayout(1, false));
314         mTop.setLayoutData(new GridData(GridData.FILL_BOTH));
315 
316         mUpdateStatus = new Label(mTop, SWT.NONE);
317         setUpdateStatus(NOT_SELECTED);
318 
319         Composite summarySection = new Composite(mTop, SWT.NONE);
320         summarySection.setLayout(gl = new GridLayout(2, false));
321         gl.marginHeight = gl.marginWidth = 0;
322 
323         mHeapSummary = createSummaryTable(summarySection);
324         mGcButton = new Button(summarySection, SWT.PUSH);
325         mGcButton.setText("Cause GC");
326         mGcButton.setEnabled(false);
327         mGcButton.addSelectionListener(new SelectionAdapter() {
328             @Override
329             public void widgetSelected(SelectionEvent e) {
330                 Client client = getCurrentClient();
331                 if (client != null) {
332                     client.executeGarbageCollector();
333                 }
334             }
335         });
336 
337         Composite comboSection = new Composite(mTop, SWT.NONE);
338         gl = new GridLayout(2, false);
339         gl.marginHeight = gl.marginWidth = 0;
340         comboSection.setLayout(gl);
341 
342         Label displayLabel = new Label(comboSection, SWT.NONE);
343         displayLabel.setText("Display: ");
344 
345         mDisplayMode = new Combo(comboSection, SWT.READ_ONLY);
346         mDisplayMode.setEnabled(false);
347         mDisplayMode.add("Stats");
348         if (DISPLAY_HEAP_BITMAP) {
349             mDisplayMode.add("Linear");
350             if (DISPLAY_HILBERT_BITMAP) {
351                 mDisplayMode.add("Hilbert");
352             }
353         }
354 
355         // the base of the displays.
356         mDisplayBase = new Composite(mTop, SWT.NONE);
357         mDisplayBase.setLayoutData(new GridData(GridData.FILL_BOTH));
358         mDisplayStack = new StackLayout();
359         mDisplayBase.setLayout(mDisplayStack);
360 
361         // create the statistics display
362         mStatisticsBase = new Composite(mDisplayBase, SWT.NONE);
363         //mStatisticsBase.setLayoutData(new GridData(GridData.FILL_BOTH));
364         mStatisticsBase.setLayout(gl = new GridLayout(1, false));
365         gl.marginHeight = gl.marginWidth = 0;
366         mDisplayStack.topControl = mStatisticsBase;
367 
368         mStatisticsTable = createDetailedTable(mStatisticsBase);
369         mStatisticsTable.setLayoutData(new GridData(GridData.FILL_BOTH));
370 
371         createChart();
372 
373         //create the linear composite
374         mLinearBase = new Composite(mDisplayBase, SWT.NONE);
375         //mLinearBase.setLayoutData(new GridData());
376         gl = new GridLayout(1, false);
377         gl.marginHeight = gl.marginWidth = 0;
378         mLinearBase.setLayout(gl);
379 
380         {
381             mLinearHeapImage = new Label(mLinearBase, SWT.NONE);
382             mLinearHeapImage.setLayoutData(new GridData());
383             mLinearHeapImage.setImage(ImageHelper.createPlaceHolderArt(mDisplay,
384                     PLACEHOLDER_LINEAR_H_SIZE, PLACEHOLDER_LINEAR_V_SIZE,
385                     mDisplay.getSystemColor(SWT.COLOR_BLUE)));
386 
387             // create a composite to contain the bottom part (legend)
388             Composite bottomSection = new Composite(mLinearBase, SWT.NONE);
389             gl = new GridLayout(1, false);
390             gl.marginHeight = gl.marginWidth = 0;
391             bottomSection.setLayout(gl);
392 
393             createLegend(bottomSection);
394         }
395 
396 /*
397         mScrolledComposite = new ScrolledComposite(mTop, SWT.H_SCROLL | SWT.V_SCROLL);
398         mScrolledComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
399         mScrolledComposite.setExpandHorizontal(true);
400         mScrolledComposite.setExpandVertical(true);
401         mScrolledComposite.setContent(mDisplayBase);
402 */
403 
404 
405         // create the hilbert display.
406         mHilbertBase = new Composite(mDisplayBase, SWT.NONE);
407         //mHilbertBase.setLayoutData(new GridData());
408         gl = new GridLayout(2, false);
409         gl.marginHeight = gl.marginWidth = 0;
410         mHilbertBase.setLayout(gl);
411 
412         if (DISPLAY_HILBERT_BITMAP) {
413             mHilbertHeapImage = new Label(mHilbertBase, SWT.NONE);
414             mHilbertHeapImage.setLayoutData(new GridData());
415             mHilbertHeapImage.setImage(ImageHelper.createPlaceHolderArt(mDisplay,
416                     PLACEHOLDER_HILBERT_SIZE, PLACEHOLDER_HILBERT_SIZE,
417                     mDisplay.getSystemColor(SWT.COLOR_BLUE)));
418 
419             // create a composite to contain the right part (legend + zoom)
420             Composite rightSection = new Composite(mHilbertBase, SWT.NONE);
421             gl = new GridLayout(1, false);
422             gl.marginHeight = gl.marginWidth = 0;
423             rightSection.setLayout(gl);
424 
425             Composite zoomComposite = new Composite(rightSection, SWT.NONE);
426             gl = new GridLayout(2, false);
427             zoomComposite.setLayout(gl);
428 
429             Label l = new Label(zoomComposite, SWT.NONE);
430             l.setText("Zoom:");
431             mZoom = new Combo(zoomComposite, SWT.READ_ONLY);
432             for (int z : ZOOMS) {
433                 mZoom.add(String.format("%1$d%%", z)); // $NON-NLS-1$
434             }
435 
436             mZoom.select(0);
437             mZoom.addSelectionListener(new SelectionAdapter() {
438                 @Override
439                 public void widgetSelected(SelectionEvent e) {
440                     setLegendText(mZoom.getSelectionIndex());
441                     Client client = getCurrentClient();
442                     if (client != null) {
443                         renderHeapData(client.getClientData(), 1, true);
444                         mTop.pack();
445                     }
446                 }
447             });
448 
449             createLegend(rightSection);
450         }
451         mHilbertBase.pack();
452 
453         mLayout = new Composite[] { mStatisticsBase, mLinearBase, mHilbertBase };
454         mDisplayMode.select(0);
455         mDisplayMode.addSelectionListener(new SelectionAdapter() {
456             @Override
457             public void widgetSelected(SelectionEvent e) {
458                 int index = mDisplayMode.getSelectionIndex();
459                 Client client = getCurrentClient();
460 
461                 if (client != null) {
462                     if (index == 0) {
463                         fillDetailedTable(client, true /* forceRedraw */);
464                     } else {
465                         renderHeapData(client.getClientData(), index-1, true /* forceRedraw */);
466                     }
467                 }
468 
469                 mDisplayStack.topControl = mLayout[index];
470                 //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
471                 //        SWT.DEFAULT));
472                 mDisplayBase.layout();
473                 //mScrolledComposite.redraw();
474             }
475         });
476 
477         //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
478         //        SWT.DEFAULT));
479         mDisplayBase.layout();
480         //mScrolledComposite.redraw();
481 
482         return mTop;
483     }
484 
485     /**
486      * Sets the focus to the proper control inside the panel.
487      */
488     @Override
setFocus()489     public void setFocus() {
490         mHeapSummary.setFocus();
491     }
492 
493 
createSummaryTable(Composite base)494     private Table createSummaryTable(Composite base) {
495         Table tab = new Table(base, SWT.SINGLE | SWT.FULL_SELECTION);
496         tab.setHeaderVisible(true);
497         tab.setLinesVisible(true);
498 
499         TableColumn col;
500 
501         col = new TableColumn(tab, SWT.RIGHT);
502         col.setText("ID");
503         col.pack();
504 
505         col = new TableColumn(tab, SWT.RIGHT);
506         col.setText("000.000WW"); // $NON-NLS-1$
507         col.pack();
508         col.setText("Heap Size");
509 
510         col = new TableColumn(tab, SWT.RIGHT);
511         col.setText("000.000WW"); // $NON-NLS-1$
512         col.pack();
513         col.setText("Allocated");
514 
515         col = new TableColumn(tab, SWT.RIGHT);
516         col.setText("000.000WW"); // $NON-NLS-1$
517         col.pack();
518         col.setText("Free");
519 
520         col = new TableColumn(tab, SWT.RIGHT);
521         col.setText("000.00%"); // $NON-NLS-1$
522         col.pack();
523         col.setText("% Used");
524 
525         col = new TableColumn(tab, SWT.RIGHT);
526         col.setText("000,000,000"); // $NON-NLS-1$
527         col.pack();
528         col.setText("# Objects");
529 
530         return tab;
531     }
532 
createDetailedTable(Composite base)533     private Table createDetailedTable(Composite base) {
534         IPreferenceStore store = DdmUiPreferences.getStore();
535 
536         Table tab = new Table(base, SWT.SINGLE | SWT.FULL_SELECTION);
537         tab.setHeaderVisible(true);
538         tab.setLinesVisible(true);
539 
540         TableHelper.createTableColumn(tab, "Type", SWT.LEFT,
541                 "4-byte array (object[], int[], float[])", //$NON-NLS-1$
542                 PREFS_STATS_COL_TYPE, store);
543 
544         TableHelper.createTableColumn(tab, "Count", SWT.RIGHT,
545                 "00,000", //$NON-NLS-1$
546                 PREFS_STATS_COL_COUNT, store);
547 
548         TableHelper.createTableColumn(tab, "Total Size", SWT.RIGHT,
549                 "000.000 WW", //$NON-NLS-1$
550                 PREFS_STATS_COL_SIZE, store);
551 
552         TableHelper.createTableColumn(tab, "Smallest", SWT.RIGHT,
553                 "000.000 WW", //$NON-NLS-1$
554                 PREFS_STATS_COL_SMALLEST, store);
555 
556         TableHelper.createTableColumn(tab, "Largest", SWT.RIGHT,
557                 "000.000 WW", //$NON-NLS-1$
558                 PREFS_STATS_COL_LARGEST, store);
559 
560         TableHelper.createTableColumn(tab, "Median", SWT.RIGHT,
561                 "000.000 WW", //$NON-NLS-1$
562                 PREFS_STATS_COL_MEDIAN, store);
563 
564         TableHelper.createTableColumn(tab, "Average", SWT.RIGHT,
565                 "000.000 WW", //$NON-NLS-1$
566                 PREFS_STATS_COL_AVERAGE, store);
567 
568         tab.addSelectionListener(new SelectionAdapter() {
569             @Override
570             public void widgetSelected(SelectionEvent e) {
571 
572                 Client client = getCurrentClient();
573                 if (client != null) {
574                     int index = mStatisticsTable.getSelectionIndex();
575                     TableItem item = mStatisticsTable.getItem(index);
576 
577                     if (item != null) {
578                         Map<Integer, ArrayList<HeapSegmentElement>> heapMap =
579                             client.getClientData().getVmHeapData().getProcessedHeapMap();
580 
581                         ArrayList<HeapSegmentElement> list = heapMap.get(item.getData());
582                         if (list != null) {
583                             showChart(list);
584                         }
585                     }
586                 }
587 
588             }
589         });
590 
591         return tab;
592     }
593 
594     /**
595      * Creates the chart below the statistics table
596      */
createChart()597     private void createChart() {
598         mAllocCountDataSet = new DefaultCategoryDataset();
599         mChart = ChartFactory.createBarChart(null, "Size", "Count", mAllocCountDataSet,
600                 PlotOrientation.VERTICAL, false, true, false);
601 
602         // get the font to make a proper title. We need to convert the swt font,
603         // into an awt font.
604         Font f = mStatisticsBase.getFont();
605         FontData[] fData = f.getFontData();
606 
607         // event though on Mac OS there could be more than one fontData, we'll only use
608         // the first one.
609         FontData firstFontData = fData[0];
610 
611         java.awt.Font awtFont = SWTUtils.toAwtFont(mStatisticsBase.getDisplay(),
612                 firstFontData, true /* ensureSameSize */);
613 
614         mChart.setTitle(new TextTitle("Allocation count per size", awtFont));
615 
616         Plot plot = mChart.getPlot();
617         if (plot instanceof CategoryPlot) {
618             // get the plot
619             CategoryPlot categoryPlot = (CategoryPlot)plot;
620 
621             // set the domain axis to draw labels that are displayed even with many values.
622             CategoryAxis domainAxis = categoryPlot.getDomainAxis();
623             domainAxis.setCategoryLabelPositions(CategoryLabelPositions.DOWN_90);
624 
625             CategoryItemRenderer renderer = categoryPlot.getRenderer();
626             renderer.setBaseToolTipGenerator(new CategoryToolTipGenerator() {
627                 public String generateToolTip(CategoryDataset dataset, int row, int column) {
628                     // get the key for the size of the allocation
629                     ByteLong columnKey = (ByteLong)dataset.getColumnKey(column);
630                     String rowKey = (String)dataset.getRowKey(row);
631                     Number value = dataset.getValue(rowKey, columnKey);
632 
633                     return String.format("%1$d %2$s of %3$d bytes", value.intValue(), rowKey,
634                             columnKey.getValue());
635                 }
636             });
637         }
638         mChartComposite = new ChartComposite(mStatisticsBase, SWT.BORDER, mChart,
639                 ChartComposite.DEFAULT_WIDTH,
640                 ChartComposite.DEFAULT_HEIGHT,
641                 ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH,
642                 ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT,
643                 3000, // max draw width. We don't want it to zoom, so we put a big number
644                 3000, // max draw height. We don't want it to zoom, so we put a big number
645                 true,  // off-screen buffer
646                 true,  // properties
647                 true,  // save
648                 true,  // print
649                 false,  // zoom
650                 true);   // tooltips
651 
652         mChartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
653     }
654 
prettyByteCount(long bytes)655     private static String prettyByteCount(long bytes) {
656         double fracBytes = bytes;
657         String units = " B";
658         if (fracBytes < 1024) {
659             return sByteFormatter.format(fracBytes) + units;
660         } else {
661             fracBytes /= 1024;
662             units = " KB";
663         }
664         if (fracBytes >= 1024) {
665             fracBytes /= 1024;
666             units = " MB";
667         }
668         if (fracBytes >= 1024) {
669             fracBytes /= 1024;
670             units = " GB";
671         }
672 
673         return sLargeByteFormatter.format(fracBytes) + units;
674     }
675 
approximateByteCount(long bytes)676     private static String approximateByteCount(long bytes) {
677         double fracBytes = bytes;
678         String units = "";
679         if (fracBytes >= 1024) {
680             fracBytes /= 1024;
681             units = "K";
682         }
683         if (fracBytes >= 1024) {
684             fracBytes /= 1024;
685             units = "M";
686         }
687         if (fracBytes >= 1024) {
688             fracBytes /= 1024;
689             units = "G";
690         }
691 
692         return sByteFormatter.format(fracBytes) + units;
693     }
694 
addCommasToNumber(long num)695     private static String addCommasToNumber(long num) {
696         return sCountFormatter.format(num);
697     }
698 
fractionalPercent(long num, long denom)699     private static String fractionalPercent(long num, long denom) {
700         double val = (double)num / (double)denom;
701         val *= 100;
702 
703         NumberFormat nf = NumberFormat.getInstance();
704         nf.setMinimumFractionDigits(2);
705         nf.setMaximumFractionDigits(2);
706         return nf.format(val) + "%";
707     }
708 
fillSummaryTable(ClientData cd)709     private void fillSummaryTable(ClientData cd) {
710         if (mHeapSummary.isDisposed()) {
711             return;
712         }
713 
714         mHeapSummary.setRedraw(false);
715         mHeapSummary.removeAll();
716 
717         if (cd != null) {
718             synchronized (cd) {
719                 Iterator<Integer> iter = cd.getVmHeapIds();
720 
721                 while (iter.hasNext()) {
722                     Integer id = iter.next();
723                     Map<String, Long> heapInfo = cd.getVmHeapInfo(id);
724                     if (heapInfo == null) {
725                         continue;
726                     }
727                     long sizeInBytes = heapInfo.get(ClientData.HEAP_SIZE_BYTES);
728                     long bytesAllocated = heapInfo.get(ClientData.HEAP_BYTES_ALLOCATED);
729                     long objectsAllocated = heapInfo.get(ClientData.HEAP_OBJECTS_ALLOCATED);
730 
731                     TableItem item = new TableItem(mHeapSummary, SWT.NONE);
732                     item.setText(0, id.toString());
733 
734                     item.setText(1, prettyByteCount(sizeInBytes));
735                     item.setText(2, prettyByteCount(bytesAllocated));
736                     item.setText(3, prettyByteCount(sizeInBytes - bytesAllocated));
737                     item.setText(4, fractionalPercent(bytesAllocated, sizeInBytes));
738                     item.setText(5, addCommasToNumber(objectsAllocated));
739                 }
740             }
741         }
742 
743         mHeapSummary.pack();
744         mHeapSummary.setRedraw(true);
745     }
746 
fillDetailedTable(Client client, boolean forceRedraw)747     private void fillDetailedTable(Client client, boolean forceRedraw) {
748         // first check if the client is invalid or heap updates are not enabled.
749         if (client == null || client.isHeapUpdateEnabled() == false) {
750             mStatisticsTable.removeAll();
751             showChart(null);
752             return;
753         }
754 
755         ClientData cd = client.getClientData();
756 
757         Map<Integer, ArrayList<HeapSegmentElement>> heapMap;
758 
759         // Atomically get and clear the heap data.
760         synchronized (cd) {
761             if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) {
762                 // no change, we return.
763                 return;
764             }
765 
766             heapMap = cd.getVmHeapData().getProcessedHeapMap();
767         }
768 
769         // we have new data, lets display it.
770 
771         // First, get the current selection, and its key.
772         int index = mStatisticsTable.getSelectionIndex();
773         Integer selectedKey = null;
774         if (index != -1) {
775             selectedKey = (Integer)mStatisticsTable.getItem(index).getData();
776         }
777 
778         // disable redraws and remove all from the table.
779         mStatisticsTable.setRedraw(false);
780         mStatisticsTable.removeAll();
781 
782         if (heapMap != null) {
783             int selectedIndex = -1;
784             ArrayList<HeapSegmentElement> selectedList = null;
785 
786             // get the keys
787             Set<Integer> keys = heapMap.keySet();
788             int iter = 0; // use a manual iter int because Set<?> doesn't have an index
789             // based accessor.
790             for (Integer key : keys) {
791                 ArrayList<HeapSegmentElement> list = heapMap.get(key);
792 
793                 // check if this is the key that is supposed to be selected
794                 if (key.equals(selectedKey)) {
795                     selectedIndex = iter;
796                     selectedList = list;
797                 }
798                 iter++;
799 
800                 TableItem item = new TableItem(mStatisticsTable, SWT.NONE);
801                 item.setData(key);
802 
803                 // get the type
804                 item.setText(0, mMapLegend[key]);
805 
806                 // set the count, smallest, largest
807                 int count = list.size();
808                 item.setText(1, addCommasToNumber(count));
809 
810                 if (count > 0) {
811                     item.setText(3, prettyByteCount(list.get(0).getLength()));
812                     item.setText(4, prettyByteCount(list.get(count-1).getLength()));
813 
814                     int median = count / 2;
815                     HeapSegmentElement element = list.get(median);
816                     long size = element.getLength();
817                     item.setText(5, prettyByteCount(size));
818 
819                     long totalSize = 0;
820                     for (int i = 0 ; i < count; i++) {
821                         element = list.get(i);
822 
823                         size = element.getLength();
824                         totalSize += size;
825                     }
826 
827                     // set the average and total
828                     item.setText(2, prettyByteCount(totalSize));
829                     item.setText(6, prettyByteCount(totalSize / count));
830                 }
831             }
832 
833             mStatisticsTable.setRedraw(true);
834 
835             if (selectedIndex != -1) {
836                 mStatisticsTable.setSelection(selectedIndex);
837                 showChart(selectedList);
838             } else {
839                 showChart(null);
840             }
841         } else {
842             mStatisticsTable.setRedraw(true);
843         }
844     }
845 
846     private static class ByteLong implements Comparable<ByteLong> {
847         private long mValue;
848 
ByteLong(long value)849         private ByteLong(long value) {
850             mValue = value;
851         }
852 
getValue()853         public long getValue() {
854             return mValue;
855         }
856 
857         @Override
toString()858         public String toString() {
859             return approximateByteCount(mValue);
860         }
861 
compareTo(ByteLong other)862         public int compareTo(ByteLong other) {
863             if (mValue != other.mValue) {
864                 return mValue < other.mValue ? -1 : 1;
865             }
866             return 0;
867         }
868 
869     }
870 
871     /**
872      * Fills the chart with the content of the list of {@link HeapSegmentElement}.
873      */
showChart(ArrayList<HeapSegmentElement> list)874     private void showChart(ArrayList<HeapSegmentElement> list) {
875         mAllocCountDataSet.clear();
876 
877         if (list != null) {
878             String rowKey = "Alloc Count";
879 
880             long currentSize = -1;
881             int currentCount = 0;
882             for (HeapSegmentElement element : list) {
883                 if (element.getLength() != currentSize) {
884                     if (currentSize != -1) {
885                         ByteLong columnKey = new ByteLong(currentSize);
886                         mAllocCountDataSet.addValue(currentCount, rowKey, columnKey);
887                     }
888 
889                     currentSize = element.getLength();
890                     currentCount = 1;
891                 } else {
892                     currentCount++;
893                 }
894             }
895 
896             // add the last item
897             if (currentSize != -1) {
898                 ByteLong columnKey = new ByteLong(currentSize);
899                 mAllocCountDataSet.addValue(currentCount, rowKey, columnKey);
900             }
901         }
902     }
903 
904     /*
905      * Add a color legend to the specified table.
906      */
createLegend(Composite parent)907     private void createLegend(Composite parent) {
908         mLegend = new Group(parent, SWT.NONE);
909         mLegend.setText(getLegendText(0));
910 
911         mLegend.setLayout(new GridLayout(2, false));
912 
913         RGB[] colors = mMapPalette.colors;
914 
915         for (int i = 0; i < NUM_PALETTE_ENTRIES; i++) {
916             Image tmpImage = createColorRect(parent.getDisplay(), colors[i]);
917 
918             Label l = new Label(mLegend, SWT.NONE);
919             l.setImage(tmpImage);
920 
921             l = new Label(mLegend, SWT.NONE);
922             l.setText(mMapLegend[i]);
923         }
924     }
925 
getLegendText(int level)926     private String getLegendText(int level) {
927         int bytes = 8 * (100 / ZOOMS[level]);
928 
929         return String.format("Key (1 pixel = %1$d bytes)", bytes);
930     }
931 
setLegendText(int level)932     private void setLegendText(int level) {
933         mLegend.setText(getLegendText(level));
934 
935     }
936 
937     /*
938      * Create a nice rectangle in the specified color.
939      */
createColorRect(Display display, RGB color)940     private Image createColorRect(Display display, RGB color) {
941         int width = 32;
942         int height = 16;
943 
944         Image img = new Image(display, width, height);
945         GC gc = new GC(img);
946         gc.setBackground(new Color(display, color));
947         gc.fillRectangle(0, 0, width, height);
948         gc.dispose();
949         return img;
950     }
951 
952 
953     /*
954      * Are updates enabled?
955      */
setUpdateStatus(int status)956     private void setUpdateStatus(int status) {
957         switch (status) {
958             case NOT_SELECTED:
959                 mUpdateStatus.setText("Select a client to see heap updates");
960                 break;
961             case NOT_ENABLED:
962                 mUpdateStatus.setText("Heap updates are " +
963                                       "NOT ENABLED for this client");
964                 break;
965             case ENABLED:
966                 mUpdateStatus.setText("Heap updates will happen after " +
967                                       "every GC for this client");
968                 break;
969             default:
970                 throw new RuntimeException();
971         }
972 
973         mUpdateStatus.pack();
974     }
975 
976 
977     /**
978      * Return the closest power of two greater than or equal to value.
979      *
980      * @param value the return value will be >= value
981      * @return a power of two >= value.  If value > 2^31, 2^31 is returned.
982      */
983 //xxx use Integer.highestOneBit() or numberOfLeadingZeros().
nextPow2(int value)984     private int nextPow2(int value) {
985         for (int i = 31; i >= 0; --i) {
986             if ((value & (1<<i)) != 0) {
987                 if (i < 31) {
988                     return 1<<(i + 1);
989                 } else {
990                     return 1<<31;
991                 }
992             }
993         }
994         return 0;
995     }
996 
zOrderData(ImageData id, byte pixData[])997     private int zOrderData(ImageData id, byte pixData[]) {
998         int maxX = 0;
999         for (int i = 0; i < pixData.length; i++) {
1000             /* Tread the pixData index as a z-order curve index and
1001              * decompose into Cartesian coordinates.
1002              */
1003             int x = (i & 1) |
1004                     ((i >>> 2) & 1) << 1 |
1005                     ((i >>> 4) & 1) << 2 |
1006                     ((i >>> 6) & 1) << 3 |
1007                     ((i >>> 8) & 1) << 4 |
1008                     ((i >>> 10) & 1) << 5 |
1009                     ((i >>> 12) & 1) << 6 |
1010                     ((i >>> 14) & 1) << 7 |
1011                     ((i >>> 16) & 1) << 8 |
1012                     ((i >>> 18) & 1) << 9 |
1013                     ((i >>> 20) & 1) << 10 |
1014                     ((i >>> 22) & 1) << 11 |
1015                     ((i >>> 24) & 1) << 12 |
1016                     ((i >>> 26) & 1) << 13 |
1017                     ((i >>> 28) & 1) << 14 |
1018                     ((i >>> 30) & 1) << 15;
1019             int y = ((i >>> 1) & 1) << 0 |
1020                     ((i >>> 3) & 1) << 1 |
1021                     ((i >>> 5) & 1) << 2 |
1022                     ((i >>> 7) & 1) << 3 |
1023                     ((i >>> 9) & 1) << 4 |
1024                     ((i >>> 11) & 1) << 5 |
1025                     ((i >>> 13) & 1) << 6 |
1026                     ((i >>> 15) & 1) << 7 |
1027                     ((i >>> 17) & 1) << 8 |
1028                     ((i >>> 19) & 1) << 9 |
1029                     ((i >>> 21) & 1) << 10 |
1030                     ((i >>> 23) & 1) << 11 |
1031                     ((i >>> 25) & 1) << 12 |
1032                     ((i >>> 27) & 1) << 13 |
1033                     ((i >>> 29) & 1) << 14 |
1034                     ((i >>> 31) & 1) << 15;
1035             try {
1036                 id.setPixel(x, y, pixData[i]);
1037                 if (x > maxX) {
1038                     maxX = x;
1039                 }
1040             } catch (IllegalArgumentException ex) {
1041                 System.out.println("bad pixels: i " + i +
1042                         ", w " + id.width +
1043                         ", h " + id.height +
1044                         ", x " + x +
1045                         ", y " + y);
1046                 throw ex;
1047             }
1048         }
1049         return maxX;
1050     }
1051 
1052     private final static int HILBERT_DIR_N = 0;
1053     private final static int HILBERT_DIR_S = 1;
1054     private final static int HILBERT_DIR_E = 2;
1055     private final static int HILBERT_DIR_W = 3;
1056 
hilbertWalk(ImageData id, InputStream pixData, int order, int x, int y, int dir)1057     private void hilbertWalk(ImageData id, InputStream pixData,
1058                              int order, int x, int y, int dir)
1059                              throws IOException {
1060         if (x >= id.width || y >= id.height) {
1061             return;
1062         } else if (order == 0) {
1063             try {
1064                 int p = pixData.read();
1065                 if (p >= 0) {
1066                     // flip along x=y axis;  assume width == height
1067                     id.setPixel(y, x, p);
1068 
1069                     /* Skanky; use an otherwise-unused ImageData field
1070                      * to keep track of the max x,y used. Note that x and y are inverted.
1071                      */
1072                     if (y > id.x) {
1073                         id.x = y;
1074                     }
1075                     if (x > id.y) {
1076                         id.y = x;
1077                     }
1078                 }
1079 //xxx just give up; don't bother walking the rest of the image
1080             } catch (IllegalArgumentException ex) {
1081                 System.out.println("bad pixels: order " + order +
1082                         ", dir " + dir +
1083                         ", w " + id.width +
1084                         ", h " + id.height +
1085                         ", x " + x +
1086                         ", y " + y);
1087                 throw ex;
1088             }
1089         } else {
1090             order--;
1091             int delta = 1 << order;
1092             int nextX = x + delta;
1093             int nextY = y + delta;
1094 
1095             switch (dir) {
1096             case HILBERT_DIR_E:
1097                 hilbertWalk(id, pixData, order,     x,     y, HILBERT_DIR_N);
1098                 hilbertWalk(id, pixData, order,     x, nextY, HILBERT_DIR_E);
1099                 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_E);
1100                 hilbertWalk(id, pixData, order, nextX,     y, HILBERT_DIR_S);
1101                 break;
1102             case HILBERT_DIR_N:
1103                 hilbertWalk(id, pixData, order,     x,     y, HILBERT_DIR_E);
1104                 hilbertWalk(id, pixData, order, nextX,     y, HILBERT_DIR_N);
1105                 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_N);
1106                 hilbertWalk(id, pixData, order,     x, nextY, HILBERT_DIR_W);
1107                 break;
1108             case HILBERT_DIR_S:
1109                 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_W);
1110                 hilbertWalk(id, pixData, order,     x, nextY, HILBERT_DIR_S);
1111                 hilbertWalk(id, pixData, order,     x,     y, HILBERT_DIR_S);
1112                 hilbertWalk(id, pixData, order, nextX,     y, HILBERT_DIR_E);
1113                 break;
1114             case HILBERT_DIR_W:
1115                 hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_S);
1116                 hilbertWalk(id, pixData, order, nextX,     y, HILBERT_DIR_W);
1117                 hilbertWalk(id, pixData, order,     x,     y, HILBERT_DIR_W);
1118                 hilbertWalk(id, pixData, order,     x, nextY, HILBERT_DIR_N);
1119                 break;
1120             default:
1121                 throw new RuntimeException("Unexpected Hilbert direction " +
1122                                            dir);
1123             }
1124         }
1125     }
1126 
hilbertOrderData(ImageData id, byte pixData[])1127     private Point hilbertOrderData(ImageData id, byte pixData[]) {
1128 
1129         int order = 0;
1130         for (int n = 1; n < id.width; n *= 2) {
1131             order++;
1132         }
1133         /* Skanky; use an otherwise-unused ImageData field
1134          * to keep track of maxX.
1135          */
1136         Point p = new Point(0,0);
1137         int oldIdX = id.x;
1138         int oldIdY = id.y;
1139         id.x = id.y = 0;
1140         try {
1141             hilbertWalk(id, new ByteArrayInputStream(pixData),
1142                         order, 0, 0, HILBERT_DIR_E);
1143             p.x = id.x;
1144             p.y = id.y;
1145         } catch (IOException ex) {
1146             System.err.println("Exception during hilbertWalk()");
1147             p.x = id.height;
1148             p.y = id.width;
1149         }
1150         id.x = oldIdX;
1151         id.y = oldIdY;
1152         return p;
1153     }
1154 
createHilbertHeapImage(byte pixData[])1155     private ImageData createHilbertHeapImage(byte pixData[]) {
1156         int w, h;
1157 
1158         // Pick an image size that the largest of heaps will fit into.
1159         w = (int)Math.sqrt((double)((16 * 1024 * 1024)/8));
1160 
1161         // Space-filling curves require a power-of-2 width.
1162         w = nextPow2(w);
1163         h = w;
1164 
1165         // Create the heap image.
1166         ImageData id = new ImageData(w, h, 8, mMapPalette);
1167 
1168         // Copy the data into the image
1169         //int maxX = zOrderData(id, pixData);
1170         Point maxP = hilbertOrderData(id, pixData);
1171 
1172         // update the max size to make it a round number once the zoom is applied
1173         int factor = 100 / ZOOMS[mZoom.getSelectionIndex()];
1174         if (factor != 1) {
1175             int tmp = maxP.x % factor;
1176             if (tmp != 0) {
1177                 maxP.x += factor - tmp;
1178             }
1179 
1180             tmp = maxP.y % factor;
1181             if (tmp != 0) {
1182                 maxP.y += factor - tmp;
1183             }
1184         }
1185 
1186         if (maxP.y < id.height) {
1187             // Crop the image down to the interesting part.
1188             id = new ImageData(id.width, maxP.y, id.depth, id.palette,
1189                                id.scanlinePad, id.data);
1190         }
1191 
1192         if (maxP.x < id.width) {
1193             // crop the image again. A bit trickier this time.
1194            ImageData croppedId = new ImageData(maxP.x, id.height, id.depth, id.palette);
1195 
1196            int[] buffer = new int[maxP.x];
1197            for (int l = 0 ; l < id.height; l++) {
1198                id.getPixels(0, l, maxP.x, buffer, 0);
1199                croppedId.setPixels(0, l, maxP.x, buffer, 0);
1200            }
1201 
1202            id = croppedId;
1203         }
1204 
1205         // apply the zoom
1206         if (factor != 1) {
1207             id = id.scaledTo(id.width / factor, id.height / factor);
1208         }
1209 
1210         return id;
1211     }
1212 
1213     /**
1214      * Convert the raw heap data to an image.  We know we're running in
1215      * the UI thread, so we can issue graphics commands directly.
1216      *
1217      * http://help.eclipse.org/help31/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html
1218      *
1219      * @param cd The client data
1220      * @param mode The display mode. 0 = linear, 1 = hilbert.
1221      * @param forceRedraw
1222      */
renderHeapData(ClientData cd, int mode, boolean forceRedraw)1223     private void renderHeapData(ClientData cd, int mode, boolean forceRedraw) {
1224         Image image;
1225 
1226         byte[] pixData;
1227 
1228         // Atomically get and clear the heap data.
1229         synchronized (cd) {
1230             if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) {
1231                 // no change, we return.
1232                 return;
1233             }
1234 
1235             pixData = getSerializedData();
1236         }
1237 
1238         if (pixData != null) {
1239             ImageData id;
1240             if (mode == 1) {
1241                 id = createHilbertHeapImage(pixData);
1242             } else {
1243                 id = createLinearHeapImage(pixData, 200, mMapPalette);
1244             }
1245 
1246             image = new Image(mDisplay, id);
1247         } else {
1248             // Render a placeholder image.
1249             int width, height;
1250             if (mode == 1) {
1251                 width = height = PLACEHOLDER_HILBERT_SIZE;
1252             } else {
1253                 width = PLACEHOLDER_LINEAR_H_SIZE;
1254                 height = PLACEHOLDER_LINEAR_V_SIZE;
1255             }
1256             image = new Image(mDisplay, width, height);
1257             GC gc = new GC(image);
1258             gc.setForeground(mDisplay.getSystemColor(SWT.COLOR_RED));
1259             gc.drawLine(0, 0, width-1, height-1);
1260             gc.dispose();
1261             gc = null;
1262         }
1263 
1264         // set the new image
1265 
1266         if (mode == 1) {
1267             if (mHilbertImage != null) {
1268                 mHilbertImage.dispose();
1269             }
1270 
1271             mHilbertImage = image;
1272             mHilbertHeapImage.setImage(mHilbertImage);
1273             mHilbertHeapImage.pack(true);
1274             mHilbertBase.layout();
1275             mHilbertBase.pack(true);
1276         } else {
1277             if (mLinearImage != null) {
1278                 mLinearImage.dispose();
1279             }
1280 
1281             mLinearImage = image;
1282             mLinearHeapImage.setImage(mLinearImage);
1283             mLinearHeapImage.pack(true);
1284             mLinearBase.layout();
1285             mLinearBase.pack(true);
1286         }
1287     }
1288 
1289     @Override
setTableFocusListener()1290     protected void setTableFocusListener() {
1291         addTableToFocusListener(mHeapSummary);
1292     }
1293 }
1294 
1295