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