Home | History | Annotate | Download | only in ddmuilib
      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      */
    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      */
    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
    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
    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
    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(ImageLoader.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(ImageLoader.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
    489     public void setFocus() {
    490         mHeapSummary.setFocus();
    491     }
    492 
    493 
    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 
    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      */
    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 
    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 
    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 
    695     private static String addCommasToNumber(long num) {
    696         return sCountFormatter.format(num);
    697     }
    698 
    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 
    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 
    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 
    849         private ByteLong(long value) {
    850             mValue = value;
    851         }
    852 
    853         public long getValue() {
    854             return mValue;
    855         }
    856 
    857         @Override
    858         public String toString() {
    859             return approximateByteCount(mValue);
    860         }
    861 
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    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().
    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 
    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 
   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 
   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 
   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      */
   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
   1290     protected void setTableFocusListener() {
   1291         addTableToFocusListener(mHeapSummary);
   1292     }
   1293 }
   1294 
   1295