2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.ddmuilib;
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;
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;
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;
78 * Base class for our information panels.
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$
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;
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();
100 private static final boolean DISPLAY_HEAP_BITMAP = false;
101 private static final boolean DISPLAY_HILBERT_BITMAP = false;
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;
107 private static final int[] ZOOMS = {100, 50, 25};
109 private static final NumberFormat sByteFormatter = NumberFormat.getInstance();
110 private static final NumberFormat sLargeByteFormatter = NumberFormat.getInstance();
111 private static final NumberFormat sCountFormatter = NumberFormat.getInstance();
114 sByteFormatter.setMinimumFractionDigits(0);
115 sByteFormatter.setMaximumFractionDigits(1);
116 sLargeByteFormatter.setMinimumFractionDigits(3);
117 sLargeByteFormatter.setMaximumFractionDigits(3);
119 sCountFormatter.setGroupingUsed(true);
122 private Display mDisplay;
124 private Composite mTop; // real top
125 private Label mUpdateStatus;
126 private Table mHeapSummary;
127 private Combo mDisplayMode;
129 //private ScrolledComposite mScrolledComposite;
131 private Composite mDisplayBase; // base of the displays.
132 private StackLayout mDisplayStack;
134 private Composite mStatisticsBase;
135 private Table mStatisticsTable;
136 private JFreeChart mChart;
137 private ChartComposite mChartComposite;
138 private Button mGcButton;
139 private DefaultCategoryDataset mAllocCountDataSet;
141 private Composite mLinearBase;
142 private Label mLinearHeapImage;
144 private Composite mHilbertBase;
145 private Label mHilbertHeapImage;
146 private Group mLegend;
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;
156 * Create color palette for map. Set up titles for legend.
158 private static PaletteData createPalette() {
159 RGB colors[] = new RGB[NUM_PALETTE_ENTRIES];
161 = new RGB(192, 192, 192); // non-heap pixels are gray
163 = "(heap expansion area)";
166 = new RGB(0, 0, 0); // free chunks are black
170 colors[HeapSegmentElement.KIND_OBJECT + 2]
171 = new RGB(0, 0, 255); // objects are blue
172 mMapLegend[HeapSegmentElement.KIND_OBJECT + 2]
175 colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
176 = new RGB(0, 255, 0); // class objects are green
177 mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
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[])";
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[])";
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[])";
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[])";
200 colors[HeapSegmentElement.KIND_UNKNOWN + 2]
201 = new RGB(255, 0, 255); // unknown objects are cyan
202 mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2]
205 colors[HeapSegmentElement.KIND_NATIVE + 2]
206 = new RGB(64, 64, 64); // native objects are dark gray
207 mMapLegend[HeapSegmentElement.KIND_NATIVE + 2]
210 return new PaletteData(colors);
214 * Sent when an existing client information changed.
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}
224 * @see IClientChangeListener#clientChanged(Client, int)
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) {
231 mTop.getDisplay().asyncExec(new Runnable() {
236 } catch (SWTException e) {
237 // display is disposed (app is quitting most likely), we do nothing.
244 * Sent when a new device is selected. The new device can be accessed
245 * with {@link #getCurrentDevice()}
248 public void deviceSelected() {
253 * Sent when a new client is selected. The new client can be accessed
254 * with {@link #getCurrentClient()}.
257 public void clientSelected() {
258 if (mTop.isDisposed())
261 Client client = getCurrentClient();
263 Log.d("ddms", "HeapPanel: changed " + client);
265 if (client != null) {
266 ClientData cd = client.getClientData();
268 if (client.isHeapUpdateEnabled()) {
269 mGcButton.setEnabled(true);
270 mDisplayMode.setEnabled(true);
271 setUpdateStatus(ENABLED);
273 setUpdateStatus(NOT_ENABLED);
274 mGcButton.setEnabled(false);
275 mDisplayMode.setEnabled(false);
278 fillSummaryTable(cd);
280 int mode = mDisplayMode.getSelectionIndex();
282 fillDetailedTable(client, false /* forceRedraw */);
284 if (DISPLAY_HEAP_BITMAP) {
285 renderHeapData(cd, mode - 1, false /* forceRedraw */);
289 mGcButton.setEnabled(false);
290 mDisplayMode.setEnabled(false);
291 fillSummaryTable(null);
292 fillDetailedTable(null, true);
293 setUpdateStatus(NOT_SELECTED);
296 // sizes of things change frequently, so redo layout
297 //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
299 mDisplayBase.layout();
300 //mScrolledComposite.redraw();
304 * Create our control(s).
307 protected Control createControl(Composite parent) {
308 mDisplay = parent.getDisplay();
312 mTop = new Composite(parent, SWT.NONE);
313 mTop.setLayout(new GridLayout(1, false));
314 mTop.setLayoutData(new GridData(GridData.FILL_BOTH));
316 mUpdateStatus = new Label(mTop, SWT.NONE);
317 setUpdateStatus(NOT_SELECTED);
319 Composite summarySection = new Composite(mTop, SWT.NONE);
320 summarySection.setLayout(gl = new GridLayout(2, false));
321 gl.marginHeight = gl.marginWidth = 0;
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() {
329 public void widgetSelected(SelectionEvent e) {
330 Client client = getCurrentClient();
331 if (client != null) {
332 client.executeGarbageCollector();
337 Composite comboSection = new Composite(mTop, SWT.NONE);
338 gl = new GridLayout(2, false);
339 gl.marginHeight = gl.marginWidth = 0;
340 comboSection.setLayout(gl);
342 Label displayLabel = new Label(comboSection, SWT.NONE);
343 displayLabel.setText("Display: ");
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");
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);
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;
368 mStatisticsTable = createDetailedTable(mStatisticsBase);
369 mStatisticsTable.setLayoutData(new GridData(GridData.FILL_BOTH));
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);
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)));
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);
393 createLegend(bottomSection);
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);
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);
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)));
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);
425 Composite zoomComposite = new Composite(rightSection, SWT.NONE);
426 gl = new GridLayout(2, false);
427 zoomComposite.setLayout(gl);
429 Label l = new Label(zoomComposite, SWT.NONE);
431 mZoom = new Combo(zoomComposite, SWT.READ_ONLY);
432 for (int z : ZOOMS) {
433 mZoom.add(String.format("%1$d%%", z)); //$NON-NLS-1$
437 mZoom.addSelectionListener(new SelectionAdapter() {
439 public void widgetSelected(SelectionEvent e) {
440 setLegendText(mZoom.getSelectionIndex());
441 Client client = getCurrentClient();
442 if (client != null) {
443 renderHeapData(client.getClientData(), 1, true);
449 createLegend(rightSection);
453 mLayout = new Composite[] { mStatisticsBase, mLinearBase, mHilbertBase };
454 mDisplayMode.select(0);
455 mDisplayMode.addSelectionListener(new SelectionAdapter() {
457 public void widgetSelected(SelectionEvent e) {
458 int index = mDisplayMode.getSelectionIndex();
459 Client client = getCurrentClient();
461 if (client != null) {
463 fillDetailedTable(client, true /* forceRedraw */);
465 renderHeapData(client.getClientData(), index-1, true /* forceRedraw */);
469 mDisplayStack.topControl = mLayout[index];
470 //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
472 mDisplayBase.layout();
473 //mScrolledComposite.redraw();
477 //mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
479 mDisplayBase.layout();
480 //mScrolledComposite.redraw();
486 * Sets the focus to the proper control inside the panel.
489 public void setFocus() {
490 mHeapSummary.setFocus();
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);
501 col = new TableColumn(tab, SWT.RIGHT);
505 col = new TableColumn(tab, SWT.RIGHT);
506 col.setText("000.000WW"); //$NON-NLS-1$
508 col.setText("Heap Size");
510 col = new TableColumn(tab, SWT.RIGHT);
511 col.setText("000.000WW"); //$NON-NLS-1$
513 col.setText("Allocated");
515 col = new TableColumn(tab, SWT.RIGHT);
516 col.setText("000.000WW"); //$NON-NLS-1$
520 col = new TableColumn(tab, SWT.RIGHT);
521 col.setText("000.00%"); //$NON-NLS-1$
523 col.setText("% Used");
525 col = new TableColumn(tab, SWT.RIGHT);
526 col.setText("000,000,000"); //$NON-NLS-1$
528 col.setText("# Objects");
533 private Table createDetailedTable(Composite base) {
534 IPreferenceStore store = DdmUiPreferences.getStore();
536 Table tab = new Table(base, SWT.SINGLE | SWT.FULL_SELECTION);
537 tab.setHeaderVisible(true);
538 tab.setLinesVisible(true);
540 TableHelper.createTableColumn(tab, "Type", SWT.LEFT,
541 "4-byte array (object[], int[], float[])", //$NON-NLS-1$
542 PREFS_STATS_COL_TYPE, store);
544 TableHelper.createTableColumn(tab, "Count", SWT.RIGHT,
545 "00,000", //$NON-NLS-1$
546 PREFS_STATS_COL_COUNT, store);
548 TableHelper.createTableColumn(tab, "Total Size", SWT.RIGHT,
549 "000.000 WW", //$NON-NLS-1$
550 PREFS_STATS_COL_SIZE, store);
552 TableHelper.createTableColumn(tab, "Smallest", SWT.RIGHT,
553 "000.000 WW", //$NON-NLS-1$
554 PREFS_STATS_COL_SMALLEST, store);
556 TableHelper.createTableColumn(tab, "Largest", SWT.RIGHT,
557 "000.000 WW", //$NON-NLS-1$
558 PREFS_STATS_COL_LARGEST, store);
560 TableHelper.createTableColumn(tab, "Median", SWT.RIGHT,
561 "000.000 WW", //$NON-NLS-1$
562 PREFS_STATS_COL_MEDIAN, store);
564 TableHelper.createTableColumn(tab, "Average", SWT.RIGHT,
565 "000.000 WW", //$NON-NLS-1$
566 PREFS_STATS_COL_AVERAGE, store);
568 tab.addSelectionListener(new SelectionAdapter() {
570 public void widgetSelected(SelectionEvent e) {
572 Client client = getCurrentClient();
573 if (client != null) {
574 int index = mStatisticsTable.getSelectionIndex();
575 TableItem item = mStatisticsTable.getItem(index);
578 Map<Integer, ArrayList<HeapSegmentElement>> heapMap =
579 client.getClientData().getVmHeapData().getProcessedHeapMap();
581 ArrayList<HeapSegmentElement> list = heapMap.get(item.getData());
595 * Creates the chart below the statistics table
597 private void createChart() {
598 mAllocCountDataSet = new DefaultCategoryDataset();
599 mChart = ChartFactory.createBarChart(null, "Size", "Count", mAllocCountDataSet,
600 PlotOrientation.VERTICAL, false, true, false);
602 // get the font to make a proper title. We need to convert the swt font,
604 Font f = mStatisticsBase.getFont();
605 FontData[] fData = f.getFontData();
607 // event though on Mac OS there could be more than one fontData, we'll only use
609 FontData firstFontData = fData[0];
611 java.awt.Font awtFont = SWTUtils.toAwtFont(mStatisticsBase.getDisplay(),
612 firstFontData, true /* ensureSameSize */);
614 mChart.setTitle(new TextTitle("Allocation count per size", awtFont));
616 Plot plot = mChart.getPlot();
617 if (plot instanceof CategoryPlot) {
619 CategoryPlot categoryPlot = (CategoryPlot)plot;
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);
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);
633 return String.format("%1$d %2$s of %3$d bytes", value.intValue(), rowKey,
634 columnKey.getValue());
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
652 mChartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
655 private static String prettyByteCount(long bytes) {
656 double fracBytes = bytes;
658 if (fracBytes < 1024) {
659 return sByteFormatter.format(fracBytes) + units;
664 if (fracBytes >= 1024) {
668 if (fracBytes >= 1024) {
673 return sLargeByteFormatter.format(fracBytes) + units;
676 private static String approximateByteCount(long bytes) {
677 double fracBytes = bytes;
679 if (fracBytes >= 1024) {
683 if (fracBytes >= 1024) {
687 if (fracBytes >= 1024) {
692 return sByteFormatter.format(fracBytes) + units;
695 private static String addCommasToNumber(long num) {
696 return sCountFormatter.format(num);
699 private static String fractionalPercent(long num, long denom) {
700 double val = (double)num / (double)denom;
703 NumberFormat nf = NumberFormat.getInstance();
704 nf.setMinimumFractionDigits(2);
705 nf.setMaximumFractionDigits(2);
706 return nf.format(val) + "%";
709 private void fillSummaryTable(ClientData cd) {
710 if (mHeapSummary.isDisposed()) {
714 mHeapSummary.setRedraw(false);
715 mHeapSummary.removeAll();
719 Iterator<Integer> iter = cd.getVmHeapIds();
721 while (iter.hasNext()) {
722 Integer id = iter.next();
723 Map<String, Long> heapInfo = cd.getVmHeapInfo(id);
724 if (heapInfo == null) {
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);
731 TableItem item = new TableItem(mHeapSummary, SWT.NONE);
732 item.setText(0, id.toString());
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));
744 mHeapSummary.setRedraw(true);
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();
755 ClientData cd = client.getClientData();
757 Map<Integer, ArrayList<HeapSegmentElement>> heapMap;
759 // Atomically get and clear the heap data.
761 if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) {
762 // no change, we return.
766 heapMap = cd.getVmHeapData().getProcessedHeapMap();
769 // we have new data, lets display it.
771 // First, get the current selection, and its key.
772 int index = mStatisticsTable.getSelectionIndex();
773 Integer selectedKey = null;
775 selectedKey = (Integer)mStatisticsTable.getItem(index).getData();
778 // disable redraws and remove all from the table.
779 mStatisticsTable.setRedraw(false);
780 mStatisticsTable.removeAll();
782 if (heapMap != null) {
783 int selectedIndex = -1;
784 ArrayList<HeapSegmentElement> selectedList = null;
787 Set<Integer> keys = heapMap.keySet();
788 int iter = 0; // use a manual iter int because Set<?> doesn't have an index
790 for (Integer key : keys) {
791 ArrayList<HeapSegmentElement> list = heapMap.get(key);
793 // check if this is the key that is supposed to be selected
794 if (key.equals(selectedKey)) {
795 selectedIndex = iter;
800 TableItem item = new TableItem(mStatisticsTable, SWT.NONE);
804 item.setText(0, mMapLegend[key]);
806 // set the count, smallest, largest
807 int count = list.size();
808 item.setText(1, addCommasToNumber(count));
811 item.setText(3, prettyByteCount(list.get(0).getLength()));
812 item.setText(4, prettyByteCount(list.get(count-1).getLength()));
814 int median = count / 2;
815 HeapSegmentElement element = list.get(median);
816 long size = element.getLength();
817 item.setText(5, prettyByteCount(size));
820 for (int i = 0 ; i < count; i++) {
821 element = list.get(i);
823 size = element.getLength();
827 // set the average and total
828 item.setText(2, prettyByteCount(totalSize));
829 item.setText(6, prettyByteCount(totalSize / count));
833 mStatisticsTable.setRedraw(true);
835 if (selectedIndex != -1) {
836 mStatisticsTable.setSelection(selectedIndex);
837 showChart(selectedList);
842 mStatisticsTable.setRedraw(true);
846 private static class ByteLong implements Comparable<ByteLong> {
849 private ByteLong(long value) {
853 public long getValue() {
858 public String toString() {
859 return approximateByteCount(mValue);
862 public int compareTo(ByteLong other) {
863 if (mValue != other.mValue) {
864 return mValue < other.mValue ? -1 : 1;
872 * Fills the chart with the content of the list of {@link HeapSegmentElement}.
874 private void showChart(ArrayList<HeapSegmentElement> list) {
875 mAllocCountDataSet.clear();
878 String rowKey = "Alloc Count";
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);
889 currentSize = element.getLength();
897 if (currentSize != -1) {
898 ByteLong columnKey = new ByteLong(currentSize);
899 mAllocCountDataSet.addValue(currentCount, rowKey, columnKey);
905 * Add a color legend to the specified table.
907 private void createLegend(Composite parent) {
908 mLegend = new Group(parent, SWT.NONE);
909 mLegend.setText(getLegendText(0));
911 mLegend.setLayout(new GridLayout(2, false));
913 RGB[] colors = mMapPalette.colors;
915 for (int i = 0; i < NUM_PALETTE_ENTRIES; i++) {
916 Image tmpImage = createColorRect(parent.getDisplay(), colors[i]);
918 Label l = new Label(mLegend, SWT.NONE);
919 l.setImage(tmpImage);
921 l = new Label(mLegend, SWT.NONE);
922 l.setText(mMapLegend[i]);
926 private String getLegendText(int level) {
927 int bytes = 8 * (100 / ZOOMS[level]);
929 return String.format("Key (1 pixel = %1$d bytes)", bytes);
932 private void setLegendText(int level) {
933 mLegend.setText(getLegendText(level));
938 * Create a nice rectangle in the specified color.
940 private Image createColorRect(Display display, RGB color) {
944 Image img = new Image(display, width, height);
946 gc.setBackground(new Color(display, color));
947 gc.fillRectangle(0, 0, width, height);
954 * Are updates enabled?
956 private void setUpdateStatus(int status) {
959 mUpdateStatus.setText("Select a client to see heap updates");
962 mUpdateStatus.setText("Heap updates are " +
963 "NOT ENABLED for this client");
966 mUpdateStatus.setText("Heap updates will happen after " +
967 "every GC for this client");
970 throw new RuntimeException();
973 mUpdateStatus.pack();
978 * Return the closest power of two greater than or equal to value.
980 * @param value the return value will be >= value
981 * @return a power of two >= value. If value > 2^31, 2^31 is returned.
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) {
997 private int zOrderData(ImageData id, byte pixData[]) {
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.
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;
1036 id.setPixel(x, y, pixData[i]);
1040 } catch (IllegalArgumentException ex) {
1041 System.out.println("bad pixels: i " + i +
1043 ", h " + id.height +
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;
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) {
1062 } else if (order == 0) {
1064 int p = pixData.read();
1066 // flip along x=y axis; assume width == height
1067 id.setPixel(y, x, p);
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.
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 +
1084 ", h " + id.height +
1091 int delta = 1 << order;
1092 int nextX = x + delta;
1093 int nextY = y + delta;
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);
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);
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);
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);
1121 throw new RuntimeException("Unexpected Hilbert direction " +
1127 private Point hilbertOrderData(ImageData id, byte pixData[]) {
1130 for (int n = 1; n < id.width; n *= 2) {
1133 /* Skanky; use an otherwise-unused ImageData field
1134 * to keep track of maxX.
1136 Point p = new Point(0,0);
1141 hilbertWalk(id, new ByteArrayInputStream(pixData),
1142 order, 0, 0, HILBERT_DIR_E);
1145 } catch (IOException ex) {
1146 System.err.println("Exception during hilbertWalk()");
1155 private ImageData createHilbertHeapImage(byte pixData[]) {
1158 // Pick an image size that the largest of heaps will fit into.
1159 w = (int)Math.sqrt((double)((16 * 1024 * 1024)/8));
1161 // Space-filling curves require a power-of-2 width.
1165 // Create the heap image.
1166 ImageData id = new ImageData(w, h, 8, mMapPalette);
1168 // Copy the data into the image
1169 //int maxX = zOrderData(id, pixData);
1170 Point maxP = hilbertOrderData(id, pixData);
1172 // update the max size to make it a round number once the zoom is applied
1173 int factor = 100 / ZOOMS[mZoom.getSelectionIndex()];
1175 int tmp = maxP.x % factor;
1177 maxP.x += factor - tmp;
1180 tmp = maxP.y % factor;
1182 maxP.y += factor - tmp;
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);
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);
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);
1207 id = id.scaledTo(id.width / factor, id.height / factor);
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.
1217 * http://help.eclipse.org/help31/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html
1219 * @param cd The client data
1220 * @param mode The display mode. 0 = linear, 1 = hilbert.
1221 * @param forceRedraw
1223 private void renderHeapData(ClientData cd, int mode, boolean forceRedraw) {
1228 // Atomically get and clear the heap data.
1230 if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) {
1231 // no change, we return.
1235 pixData = getSerializedData();
1238 if (pixData != null) {
1241 id = createHilbertHeapImage(pixData);
1243 id = createLinearHeapImage(pixData, 200, mMapPalette);
1246 image = new Image(mDisplay, id);
1248 // Render a placeholder image.
1251 width = height = PLACEHOLDER_HILBERT_SIZE;
1253 width = PLACEHOLDER_LINEAR_H_SIZE;
1254 height = PLACEHOLDER_LINEAR_V_SIZE;
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);
1264 // set the new image
1267 if (mHilbertImage != null) {
1268 mHilbertImage.dispose();
1271 mHilbertImage = image;
1272 mHilbertHeapImage.setImage(mHilbertImage);
1273 mHilbertHeapImage.pack(true);
1274 mHilbertBase.layout();
1275 mHilbertBase.pack(true);
1277 if (mLinearImage != null) {
1278 mLinearImage.dispose();
1281 mLinearImage = image;
1282 mLinearHeapImage.setImage(mLinearImage);
1283 mLinearHeapImage.pack(true);
1284 mLinearBase.layout();
1285 mLinearBase.pack(true);
1290 protected void setTableFocusListener() {
1291 addTableToFocusListener(mHeapSummary);