OSDN Git Service

Merge "Add eclipse version to usage stat ping."
[android-x86/sdk.git] / ddms / libs / ddmuilib / src / com / android / ddmuilib / HeapPanel.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.ddmuilib;
18
19 import com.android.ddmlib.Client;
20 import com.android.ddmlib.ClientData;
21 import com.android.ddmlib.Log;
22 import com.android.ddmlib.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