blob: 977203b2d5e5d80945e869b521223cd52b82176e [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ddmuilib;
import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData;
import com.android.ddmlib.Log;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.HeapSegment.HeapSegmentElement;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.labels.CategoryToolTipGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.experimental.chart.swt.ChartComposite;
import org.jfree.experimental.swt.SWTUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Base class for our information panels.
*/
public final class HeapPanel extends BaseHeapPanel {
private static final String PREFS_STATS_COL_TYPE = "heapPanel.col0"; //$NON-NLS-1$
private static final String PREFS_STATS_COL_COUNT = "heapPanel.col1"; //$NON-NLS-1$
private static final String PREFS_STATS_COL_SIZE = "heapPanel.col2"; //$NON-NLS-1$
private static final String PREFS_STATS_COL_SMALLEST = "heapPanel.col3"; //$NON-NLS-1$
private static final String PREFS_STATS_COL_LARGEST = "heapPanel.col4"; //$NON-NLS-1$
private static final String PREFS_STATS_COL_MEDIAN = "heapPanel.col5"; //$NON-NLS-1$
private static final String PREFS_STATS_COL_AVERAGE = "heapPanel.col6"; //$NON-NLS-1$
/* args to setUpdateStatus() */
private static final int NOT_SELECTED = 0;
private static final int NOT_ENABLED = 1;
private static final int ENABLED = 2;
/** color palette and map legend. NATIVE is the last enum is a 0 based enum list, so we need
* Native+1 at least. We also need 2 more entries for free area and expansion area. */
private static final int NUM_PALETTE_ENTRIES = HeapSegmentElement.KIND_NATIVE+2 +1;
private static final String[] mMapLegend = new String[NUM_PALETTE_ENTRIES];
private static final PaletteData mMapPalette = createPalette();
private static final boolean DISPLAY_HEAP_BITMAP = false;
private static final boolean DISPLAY_HILBERT_BITMAP = false;
private static final int PLACEHOLDER_HILBERT_SIZE = 200;
private static final int PLACEHOLDER_LINEAR_V_SIZE = 100;
private static final int PLACEHOLDER_LINEAR_H_SIZE = 300;
private static final int[] ZOOMS = {100, 50, 25};
private static final NumberFormat sByteFormatter = NumberFormat.getInstance();
private static final NumberFormat sLargeByteFormatter = NumberFormat.getInstance();
private static final NumberFormat sCountFormatter = NumberFormat.getInstance();
static {
sByteFormatter.setMinimumFractionDigits(0);
sByteFormatter.setMaximumFractionDigits(1);
sLargeByteFormatter.setMinimumFractionDigits(3);
sLargeByteFormatter.setMaximumFractionDigits(3);
sCountFormatter.setGroupingUsed(true);
}
private Display mDisplay;
private Composite mTop; // real top
private Label mUpdateStatus;
private Table mHeapSummary;
private Combo mDisplayMode;
//private ScrolledComposite mScrolledComposite;
private Composite mDisplayBase; // base of the displays.
private StackLayout mDisplayStack;
private Composite mStatisticsBase;
private Table mStatisticsTable;
private JFreeChart mChart;
private ChartComposite mChartComposite;
private Button mGcButton;
private DefaultCategoryDataset mAllocCountDataSet;
private Composite mLinearBase;
private Label mLinearHeapImage;
private Composite mHilbertBase;
private Label mHilbertHeapImage;
private Group mLegend;
private Combo mZoom;
/** Image used for the hilbert display. Since we recreate a new image every time, we
* keep this one around to dispose it. */
private Image mHilbertImage;
private Image mLinearImage;
private Composite[] mLayout;
/*
* Create color palette for map. Set up titles for legend.
*/
private static PaletteData createPalette() {
RGB colors[] = new RGB[NUM_PALETTE_ENTRIES];
colors[0]
= new RGB(192, 192, 192); // non-heap pixels are gray
mMapLegend[0]
= "(heap expansion area)";
colors[1]
= new RGB(0, 0, 0); // free chunks are black
mMapLegend[1]
= "free";
colors[HeapSegmentElement.KIND_OBJECT + 2]
= new RGB(0, 0, 255); // objects are blue
mMapLegend[HeapSegmentElement.KIND_OBJECT + 2]
= "data object";
colors[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
= new RGB(0, 255, 0); // class objects are green
mMapLegend[HeapSegmentElement.KIND_CLASS_OBJECT + 2]
= "class object";
colors[HeapSegmentElement.KIND_ARRAY_1 + 2]
= new RGB(255, 0, 0); // byte/bool arrays are red
mMapLegend[HeapSegmentElement.KIND_ARRAY_1 + 2]
= "1-byte array (byte[], boolean[])";
colors[HeapSegmentElement.KIND_ARRAY_2 + 2]
= new RGB(255, 128, 0); // short/char arrays are orange
mMapLegend[HeapSegmentElement.KIND_ARRAY_2 + 2]
= "2-byte array (short[], char[])";
colors[HeapSegmentElement.KIND_ARRAY_4 + 2]
= new RGB(255, 255, 0); // obj/int/float arrays are yellow
mMapLegend[HeapSegmentElement.KIND_ARRAY_4 + 2]
= "4-byte array (object[], int[], float[])";
colors[HeapSegmentElement.KIND_ARRAY_8 + 2]
= new RGB(255, 128, 128); // long/double arrays are pink
mMapLegend[HeapSegmentElement.KIND_ARRAY_8 + 2]
= "8-byte array (long[], double[])";
colors[HeapSegmentElement.KIND_UNKNOWN + 2]
= new RGB(255, 0, 255); // unknown objects are cyan
mMapLegend[HeapSegmentElement.KIND_UNKNOWN + 2]
= "unknown object";
colors[HeapSegmentElement.KIND_NATIVE + 2]
= new RGB(64, 64, 64); // native objects are dark gray
mMapLegend[HeapSegmentElement.KIND_NATIVE + 2]
= "non-Java object";
return new PaletteData(colors);
}
/**
* Sent when an existing client information changed.
* <p/>
* This is sent from a non UI thread.
* @param client the updated client.
* @param changeMask the bit mask describing the changed properties. It can contain
* any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME}
* {@link Client#CHANGE_DEBUGGER_INTEREST}, {@link Client#CHANGE_THREAD_MODE},
* {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
* {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
*
* @see IClientChangeListener#clientChanged(Client, int)
*/
public void clientChanged(final Client client, int changeMask) {
if (client == getCurrentClient()) {
if ((changeMask & Client.CHANGE_HEAP_MODE) == Client.CHANGE_HEAP_MODE ||
(changeMask & Client.CHANGE_HEAP_DATA) == Client.CHANGE_HEAP_DATA) {
try {
mTop.getDisplay().asyncExec(new Runnable() {
public void run() {
clientSelected();
}
});
} catch (SWTException e) {
// display is disposed (app is quitting most likely), we do nothing.
}
}
}
}
/**
* Sent when a new device is selected. The new device can be accessed
* with {@link #getCurrentDevice()}
*/
@Override
public void deviceSelected() {
// pass
}
/**
* Sent when a new client is selected. The new client can be accessed
* with {@link #getCurrentClient()}.
*/
@Override
public void clientSelected() {
if (mTop.isDisposed())
return;
Client client = getCurrentClient();
Log.d("ddms", "HeapPanel: changed " + client);
if (client != null) {
ClientData cd = client.getClientData();
if (client.isHeapUpdateEnabled()) {
mGcButton.setEnabled(true);
mDisplayMode.setEnabled(true);
setUpdateStatus(ENABLED);
} else {
setUpdateStatus(NOT_ENABLED);
mGcButton.setEnabled(false);
mDisplayMode.setEnabled(false);
}
fillSummaryTable(cd);
int mode = mDisplayMode.getSelectionIndex();
if (mode == 0) {
fillDetailedTable(client, false /* forceRedraw */);
} else {
if (DISPLAY_HEAP_BITMAP) {
renderHeapData(cd, mode - 1, false /* forceRedraw */);
}
}
} else {
mGcButton.setEnabled(false);
mDisplayMode.setEnabled(false);
fillSummaryTable(null);
fillDetailedTable(null, true);
setUpdateStatus(NOT_SELECTED);
}
// sizes of things change frequently, so redo layout
//mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
// SWT.DEFAULT));
mDisplayBase.layout();
//mScrolledComposite.redraw();
}
/**
* Create our control(s).
*/
@Override
protected Control createControl(Composite parent) {
mDisplay = parent.getDisplay();
GridLayout gl;
mTop = new Composite(parent, SWT.NONE);
mTop.setLayout(new GridLayout(1, false));
mTop.setLayoutData(new GridData(GridData.FILL_BOTH));
mUpdateStatus = new Label(mTop, SWT.NONE);
setUpdateStatus(NOT_SELECTED);
Composite summarySection = new Composite(mTop, SWT.NONE);
summarySection.setLayout(gl = new GridLayout(2, false));
gl.marginHeight = gl.marginWidth = 0;
mHeapSummary = createSummaryTable(summarySection);
mGcButton = new Button(summarySection, SWT.PUSH);
mGcButton.setText("Cause GC");
mGcButton.setEnabled(false);
mGcButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Client client = getCurrentClient();
if (client != null) {
client.executeGarbageCollector();
}
}
});
Composite comboSection = new Composite(mTop, SWT.NONE);
gl = new GridLayout(2, false);
gl.marginHeight = gl.marginWidth = 0;
comboSection.setLayout(gl);
Label displayLabel = new Label(comboSection, SWT.NONE);
displayLabel.setText("Display: ");
mDisplayMode = new Combo(comboSection, SWT.READ_ONLY);
mDisplayMode.setEnabled(false);
mDisplayMode.add("Stats");
if (DISPLAY_HEAP_BITMAP) {
mDisplayMode.add("Linear");
if (DISPLAY_HILBERT_BITMAP) {
mDisplayMode.add("Hilbert");
}
}
// the base of the displays.
mDisplayBase = new Composite(mTop, SWT.NONE);
mDisplayBase.setLayoutData(new GridData(GridData.FILL_BOTH));
mDisplayStack = new StackLayout();
mDisplayBase.setLayout(mDisplayStack);
// create the statistics display
mStatisticsBase = new Composite(mDisplayBase, SWT.NONE);
//mStatisticsBase.setLayoutData(new GridData(GridData.FILL_BOTH));
mStatisticsBase.setLayout(gl = new GridLayout(1, false));
gl.marginHeight = gl.marginWidth = 0;
mDisplayStack.topControl = mStatisticsBase;
mStatisticsTable = createDetailedTable(mStatisticsBase);
mStatisticsTable.setLayoutData(new GridData(GridData.FILL_BOTH));
createChart();
//create the linear composite
mLinearBase = new Composite(mDisplayBase, SWT.NONE);
//mLinearBase.setLayoutData(new GridData());
gl = new GridLayout(1, false);
gl.marginHeight = gl.marginWidth = 0;
mLinearBase.setLayout(gl);
{
mLinearHeapImage = new Label(mLinearBase, SWT.NONE);
mLinearHeapImage.setLayoutData(new GridData());
mLinearHeapImage.setImage(ImageHelper.createPlaceHolderArt(mDisplay,
PLACEHOLDER_LINEAR_H_SIZE, PLACEHOLDER_LINEAR_V_SIZE,
mDisplay.getSystemColor(SWT.COLOR_BLUE)));
// create a composite to contain the bottom part (legend)
Composite bottomSection = new Composite(mLinearBase, SWT.NONE);
gl = new GridLayout(1, false);
gl.marginHeight = gl.marginWidth = 0;
bottomSection.setLayout(gl);
createLegend(bottomSection);
}
/*
mScrolledComposite = new ScrolledComposite(mTop, SWT.H_SCROLL | SWT.V_SCROLL);
mScrolledComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
mScrolledComposite.setExpandHorizontal(true);
mScrolledComposite.setExpandVertical(true);
mScrolledComposite.setContent(mDisplayBase);
*/
// create the hilbert display.
mHilbertBase = new Composite(mDisplayBase, SWT.NONE);
//mHilbertBase.setLayoutData(new GridData());
gl = new GridLayout(2, false);
gl.marginHeight = gl.marginWidth = 0;
mHilbertBase.setLayout(gl);
if (DISPLAY_HILBERT_BITMAP) {
mHilbertHeapImage = new Label(mHilbertBase, SWT.NONE);
mHilbertHeapImage.setLayoutData(new GridData());
mHilbertHeapImage.setImage(ImageHelper.createPlaceHolderArt(mDisplay,
PLACEHOLDER_HILBERT_SIZE, PLACEHOLDER_HILBERT_SIZE,
mDisplay.getSystemColor(SWT.COLOR_BLUE)));
// create a composite to contain the right part (legend + zoom)
Composite rightSection = new Composite(mHilbertBase, SWT.NONE);
gl = new GridLayout(1, false);
gl.marginHeight = gl.marginWidth = 0;
rightSection.setLayout(gl);
Composite zoomComposite = new Composite(rightSection, SWT.NONE);
gl = new GridLayout(2, false);
zoomComposite.setLayout(gl);
Label l = new Label(zoomComposite, SWT.NONE);
l.setText("Zoom:");
mZoom = new Combo(zoomComposite, SWT.READ_ONLY);
for (int z : ZOOMS) {
mZoom.add(String.format("%1$d%%", z)); // $NON-NLS-1$
}
mZoom.select(0);
mZoom.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
setLegendText(mZoom.getSelectionIndex());
Client client = getCurrentClient();
if (client != null) {
renderHeapData(client.getClientData(), 1, true);
mTop.pack();
}
}
});
createLegend(rightSection);
}
mHilbertBase.pack();
mLayout = new Composite[] { mStatisticsBase, mLinearBase, mHilbertBase };
mDisplayMode.select(0);
mDisplayMode.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int index = mDisplayMode.getSelectionIndex();
Client client = getCurrentClient();
if (client != null) {
if (index == 0) {
fillDetailedTable(client, true /* forceRedraw */);
} else {
renderHeapData(client.getClientData(), index-1, true /* forceRedraw */);
}
}
mDisplayStack.topControl = mLayout[index];
//mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
// SWT.DEFAULT));
mDisplayBase.layout();
//mScrolledComposite.redraw();
}
});
//mScrolledComposite.setMinSize(mDisplayStack.topControl.computeSize(SWT.DEFAULT,
// SWT.DEFAULT));
mDisplayBase.layout();
//mScrolledComposite.redraw();
return mTop;
}
/**
* Sets the focus to the proper control inside the panel.
*/
@Override
public void setFocus() {
mHeapSummary.setFocus();
}
private Table createSummaryTable(Composite base) {
Table tab = new Table(base, SWT.SINGLE | SWT.FULL_SELECTION);
tab.setHeaderVisible(true);
tab.setLinesVisible(true);
TableColumn col;
col = new TableColumn(tab, SWT.RIGHT);
col.setText("ID");
col.pack();
col = new TableColumn(tab, SWT.RIGHT);
col.setText("000.000WW"); // $NON-NLS-1$
col.pack();
col.setText("Heap Size");
col = new TableColumn(tab, SWT.RIGHT);
col.setText("000.000WW"); // $NON-NLS-1$
col.pack();
col.setText("Allocated");
col = new TableColumn(tab, SWT.RIGHT);
col.setText("000.000WW"); // $NON-NLS-1$
col.pack();
col.setText("Free");
col = new TableColumn(tab, SWT.RIGHT);
col.setText("000.00%"); // $NON-NLS-1$
col.pack();
col.setText("% Used");
col = new TableColumn(tab, SWT.RIGHT);
col.setText("000,000,000"); // $NON-NLS-1$
col.pack();
col.setText("# Objects");
return tab;
}
private Table createDetailedTable(Composite base) {
IPreferenceStore store = DdmUiPreferences.getStore();
Table tab = new Table(base, SWT.SINGLE | SWT.FULL_SELECTION);
tab.setHeaderVisible(true);
tab.setLinesVisible(true);
TableHelper.createTableColumn(tab, "Type", SWT.LEFT,
"4-byte array (object[], int[], float[])", //$NON-NLS-1$
PREFS_STATS_COL_TYPE, store);
TableHelper.createTableColumn(tab, "Count", SWT.RIGHT,
"00,000", //$NON-NLS-1$
PREFS_STATS_COL_COUNT, store);
TableHelper.createTableColumn(tab, "Total Size", SWT.RIGHT,
"000.000 WW", //$NON-NLS-1$
PREFS_STATS_COL_SIZE, store);
TableHelper.createTableColumn(tab, "Smallest", SWT.RIGHT,
"000.000 WW", //$NON-NLS-1$
PREFS_STATS_COL_SMALLEST, store);
TableHelper.createTableColumn(tab, "Largest", SWT.RIGHT,
"000.000 WW", //$NON-NLS-1$
PREFS_STATS_COL_LARGEST, store);
TableHelper.createTableColumn(tab, "Median", SWT.RIGHT,
"000.000 WW", //$NON-NLS-1$
PREFS_STATS_COL_MEDIAN, store);
TableHelper.createTableColumn(tab, "Average", SWT.RIGHT,
"000.000 WW", //$NON-NLS-1$
PREFS_STATS_COL_AVERAGE, store);
tab.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
Client client = getCurrentClient();
if (client != null) {
int index = mStatisticsTable.getSelectionIndex();
TableItem item = mStatisticsTable.getItem(index);
if (item != null) {
Map<Integer, ArrayList<HeapSegmentElement>> heapMap =
client.getClientData().getVmHeapData().getProcessedHeapMap();
ArrayList<HeapSegmentElement> list = heapMap.get(item.getData());
if (list != null) {
showChart(list);
}
}
}
}
});
return tab;
}
/**
* Creates the chart below the statistics table
*/
private void createChart() {
mAllocCountDataSet = new DefaultCategoryDataset();
mChart = ChartFactory.createBarChart(null, "Size", "Count", mAllocCountDataSet,
PlotOrientation.VERTICAL, false, true, false);
// get the font to make a proper title. We need to convert the swt font,
// into an awt font.
Font f = mStatisticsBase.getFont();
FontData[] fData = f.getFontData();
// event though on Mac OS there could be more than one fontData, we'll only use
// the first one.
FontData firstFontData = fData[0];
java.awt.Font awtFont = SWTUtils.toAwtFont(mStatisticsBase.getDisplay(),
firstFontData, true /* ensureSameSize */);
mChart.setTitle(new TextTitle("Allocation count per size", awtFont));
Plot plot = mChart.getPlot();
if (plot instanceof CategoryPlot) {
// get the plot
CategoryPlot categoryPlot = (CategoryPlot)plot;
// set the domain axis to draw labels that are displayed even with many values.
CategoryAxis domainAxis = categoryPlot.getDomainAxis();
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.DOWN_90);
CategoryItemRenderer renderer = categoryPlot.getRenderer();
renderer.setBaseToolTipGenerator(new CategoryToolTipGenerator() {
public String generateToolTip(CategoryDataset dataset, int row, int column) {
// get the key for the size of the allocation
ByteLong columnKey = (ByteLong)dataset.getColumnKey(column);
String rowKey = (String)dataset.getRowKey(row);
Number value = dataset.getValue(rowKey, columnKey);
return String.format("%1$d %2$s of %3$d bytes", value.intValue(), rowKey,
columnKey.getValue());
}
});
}
mChartComposite = new ChartComposite(mStatisticsBase, SWT.BORDER, mChart,
ChartComposite.DEFAULT_WIDTH,
ChartComposite.DEFAULT_HEIGHT,
ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH,
ChartComposite.DEFAULT_MINIMUM_DRAW_HEIGHT,
3000, // max draw width. We don't want it to zoom, so we put a big number
3000, // max draw height. We don't want it to zoom, so we put a big number
true, // off-screen buffer
true, // properties
true, // save
true, // print
false, // zoom
true); // tooltips
mChartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
}
private static String prettyByteCount(long bytes) {
double fracBytes = bytes;
String units = " B";
if (fracBytes < 1024) {
return sByteFormatter.format(fracBytes) + units;
} else {
fracBytes /= 1024;
units = " KB";
}
if (fracBytes >= 1024) {
fracBytes /= 1024;
units = " MB";
}
if (fracBytes >= 1024) {
fracBytes /= 1024;
units = " GB";
}
return sLargeByteFormatter.format(fracBytes) + units;
}
private static String approximateByteCount(long bytes) {
double fracBytes = bytes;
String units = "";
if (fracBytes >= 1024) {
fracBytes /= 1024;
units = "K";
}
if (fracBytes >= 1024) {
fracBytes /= 1024;
units = "M";
}
if (fracBytes >= 1024) {
fracBytes /= 1024;
units = "G";
}
return sByteFormatter.format(fracBytes) + units;
}
private static String addCommasToNumber(long num) {
return sCountFormatter.format(num);
}
private static String fractionalPercent(long num, long denom) {
double val = (double)num / (double)denom;
val *= 100;
NumberFormat nf = NumberFormat.getInstance();
nf.setMinimumFractionDigits(2);
nf.setMaximumFractionDigits(2);
return nf.format(val) + "%";
}
private void fillSummaryTable(ClientData cd) {
if (mHeapSummary.isDisposed()) {
return;
}
mHeapSummary.setRedraw(false);
mHeapSummary.removeAll();
if (cd != null) {
synchronized (cd) {
Iterator<Integer> iter = cd.getVmHeapIds();
while (iter.hasNext()) {
Integer id = iter.next();
Map<String, Long> heapInfo = cd.getVmHeapInfo(id);
if (heapInfo == null) {
continue;
}
long sizeInBytes = heapInfo.get(ClientData.HEAP_SIZE_BYTES);
long bytesAllocated = heapInfo.get(ClientData.HEAP_BYTES_ALLOCATED);
long objectsAllocated = heapInfo.get(ClientData.HEAP_OBJECTS_ALLOCATED);
TableItem item = new TableItem(mHeapSummary, SWT.NONE);
item.setText(0, id.toString());
item.setText(1, prettyByteCount(sizeInBytes));
item.setText(2, prettyByteCount(bytesAllocated));
item.setText(3, prettyByteCount(sizeInBytes - bytesAllocated));
item.setText(4, fractionalPercent(bytesAllocated, sizeInBytes));
item.setText(5, addCommasToNumber(objectsAllocated));
}
}
}
mHeapSummary.pack();
mHeapSummary.setRedraw(true);
}
private void fillDetailedTable(Client client, boolean forceRedraw) {
// first check if the client is invalid or heap updates are not enabled.
if (client == null || client.isHeapUpdateEnabled() == false) {
mStatisticsTable.removeAll();
showChart(null);
return;
}
ClientData cd = client.getClientData();
Map<Integer, ArrayList<HeapSegmentElement>> heapMap;
// Atomically get and clear the heap data.
synchronized (cd) {
if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) {
// no change, we return.
return;
}
heapMap = cd.getVmHeapData().getProcessedHeapMap();
}
// we have new data, lets display it.
// First, get the current selection, and its key.
int index = mStatisticsTable.getSelectionIndex();
Integer selectedKey = null;
if (index != -1) {
selectedKey = (Integer)mStatisticsTable.getItem(index).getData();
}
// disable redraws and remove all from the table.
mStatisticsTable.setRedraw(false);
mStatisticsTable.removeAll();
if (heapMap != null) {
int selectedIndex = -1;
ArrayList<HeapSegmentElement> selectedList = null;
// get the keys
Set<Integer> keys = heapMap.keySet();
int iter = 0; // use a manual iter int because Set<?> doesn't have an index
// based accessor.
for (Integer key : keys) {
ArrayList<HeapSegmentElement> list = heapMap.get(key);
// check if this is the key that is supposed to be selected
if (key.equals(selectedKey)) {
selectedIndex = iter;
selectedList = list;
}
iter++;
TableItem item = new TableItem(mStatisticsTable, SWT.NONE);
item.setData(key);
// get the type
item.setText(0, mMapLegend[key]);
// set the count, smallest, largest
int count = list.size();
item.setText(1, addCommasToNumber(count));
if (count > 0) {
item.setText(3, prettyByteCount(list.get(0).getLength()));
item.setText(4, prettyByteCount(list.get(count-1).getLength()));
int median = count / 2;
HeapSegmentElement element = list.get(median);
long size = element.getLength();
item.setText(5, prettyByteCount(size));
long totalSize = 0;
for (int i = 0 ; i < count; i++) {
element = list.get(i);
size = element.getLength();
totalSize += size;
}
// set the average and total
item.setText(2, prettyByteCount(totalSize));
item.setText(6, prettyByteCount(totalSize / count));
}
}
mStatisticsTable.setRedraw(true);
if (selectedIndex != -1) {
mStatisticsTable.setSelection(selectedIndex);
showChart(selectedList);
} else {
showChart(null);
}
} else {
mStatisticsTable.setRedraw(true);
}
}
private static class ByteLong implements Comparable<ByteLong> {
private long mValue;
private ByteLong(long value) {
mValue = value;
}
public long getValue() {
return mValue;
}
@Override
public String toString() {
return approximateByteCount(mValue);
}
public int compareTo(ByteLong other) {
if (mValue != other.mValue) {
return mValue < other.mValue ? -1 : 1;
}
return 0;
}
}
/**
* Fills the chart with the content of the list of {@link HeapSegmentElement}.
*/
private void showChart(ArrayList<HeapSegmentElement> list) {
mAllocCountDataSet.clear();
if (list != null) {
String rowKey = "Alloc Count";
long currentSize = -1;
int currentCount = 0;
for (HeapSegmentElement element : list) {
if (element.getLength() != currentSize) {
if (currentSize != -1) {
ByteLong columnKey = new ByteLong(currentSize);
mAllocCountDataSet.addValue(currentCount, rowKey, columnKey);
}
currentSize = element.getLength();
currentCount = 1;
} else {
currentCount++;
}
}
// add the last item
if (currentSize != -1) {
ByteLong columnKey = new ByteLong(currentSize);
mAllocCountDataSet.addValue(currentCount, rowKey, columnKey);
}
}
}
/*
* Add a color legend to the specified table.
*/
private void createLegend(Composite parent) {
mLegend = new Group(parent, SWT.NONE);
mLegend.setText(getLegendText(0));
mLegend.setLayout(new GridLayout(2, false));
RGB[] colors = mMapPalette.colors;
for (int i = 0; i < NUM_PALETTE_ENTRIES; i++) {
Image tmpImage = createColorRect(parent.getDisplay(), colors[i]);
Label l = new Label(mLegend, SWT.NONE);
l.setImage(tmpImage);
l = new Label(mLegend, SWT.NONE);
l.setText(mMapLegend[i]);
}
}
private String getLegendText(int level) {
int bytes = 8 * (100 / ZOOMS[level]);
return String.format("Key (1 pixel = %1$d bytes)", bytes);
}
private void setLegendText(int level) {
mLegend.setText(getLegendText(level));
}
/*
* Create a nice rectangle in the specified color.
*/
private Image createColorRect(Display display, RGB color) {
int width = 32;
int height = 16;
Image img = new Image(display, width, height);
GC gc = new GC(img);
gc.setBackground(new Color(display, color));
gc.fillRectangle(0, 0, width, height);
gc.dispose();
return img;
}
/*
* Are updates enabled?
*/
private void setUpdateStatus(int status) {
switch (status) {
case NOT_SELECTED:
mUpdateStatus.setText("Select a client to see heap updates");
break;
case NOT_ENABLED:
mUpdateStatus.setText("Heap updates are " +
"NOT ENABLED for this client");
break;
case ENABLED:
mUpdateStatus.setText("Heap updates will happen after " +
"every GC for this client");
break;
default:
throw new RuntimeException();
}
mUpdateStatus.pack();
}
/**
* Return the closest power of two greater than or equal to value.
*
* @param value the return value will be >= value
* @return a power of two >= value. If value > 2^31, 2^31 is returned.
*/
//xxx use Integer.highestOneBit() or numberOfLeadingZeros().
private int nextPow2(int value) {
for (int i = 31; i >= 0; --i) {
if ((value & (1<<i)) != 0) {
if (i < 31) {
return 1<<(i + 1);
} else {
return 1<<31;
}
}
}
return 0;
}
private int zOrderData(ImageData id, byte pixData[]) {
int maxX = 0;
for (int i = 0; i < pixData.length; i++) {
/* Tread the pixData index as a z-order curve index and
* decompose into Cartesian coordinates.
*/
int x = (i & 1) |
((i >>> 2) & 1) << 1 |
((i >>> 4) & 1) << 2 |
((i >>> 6) & 1) << 3 |
((i >>> 8) & 1) << 4 |
((i >>> 10) & 1) << 5 |
((i >>> 12) & 1) << 6 |
((i >>> 14) & 1) << 7 |
((i >>> 16) & 1) << 8 |
((i >>> 18) & 1) << 9 |
((i >>> 20) & 1) << 10 |
((i >>> 22) & 1) << 11 |
((i >>> 24) & 1) << 12 |
((i >>> 26) & 1) << 13 |
((i >>> 28) & 1) << 14 |
((i >>> 30) & 1) << 15;
int y = ((i >>> 1) & 1) << 0 |
((i >>> 3) & 1) << 1 |
((i >>> 5) & 1) << 2 |
((i >>> 7) & 1) << 3 |
((i >>> 9) & 1) << 4 |
((i >>> 11) & 1) << 5 |
((i >>> 13) & 1) << 6 |
((i >>> 15) & 1) << 7 |
((i >>> 17) & 1) << 8 |
((i >>> 19) & 1) << 9 |
((i >>> 21) & 1) << 10 |
((i >>> 23) & 1) << 11 |
((i >>> 25) & 1) << 12 |
((i >>> 27) & 1) << 13 |
((i >>> 29) & 1) << 14 |
((i >>> 31) & 1) << 15;
try {
id.setPixel(x, y, pixData[i]);
if (x > maxX) {
maxX = x;
}
} catch (IllegalArgumentException ex) {
System.out.println("bad pixels: i " + i +
", w " + id.width +
", h " + id.height +
", x " + x +
", y " + y);
throw ex;
}
}
return maxX;
}
private final static int HILBERT_DIR_N = 0;
private final static int HILBERT_DIR_S = 1;
private final static int HILBERT_DIR_E = 2;
private final static int HILBERT_DIR_W = 3;
private void hilbertWalk(ImageData id, InputStream pixData,
int order, int x, int y, int dir)
throws IOException {
if (x >= id.width || y >= id.height) {
return;
} else if (order == 0) {
try {
int p = pixData.read();
if (p >= 0) {
// flip along x=y axis; assume width == height
id.setPixel(y, x, p);
/* Skanky; use an otherwise-unused ImageData field
* to keep track of the max x,y used. Note that x and y are inverted.
*/
if (y > id.x) {
id.x = y;
}
if (x > id.y) {
id.y = x;
}
}
//xxx just give up; don't bother walking the rest of the image
} catch (IllegalArgumentException ex) {
System.out.println("bad pixels: order " + order +
", dir " + dir +
", w " + id.width +
", h " + id.height +
", x " + x +
", y " + y);
throw ex;
}
} else {
order--;
int delta = 1 << order;
int nextX = x + delta;
int nextY = y + delta;
switch (dir) {
case HILBERT_DIR_E:
hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_N);
hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_E);
hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_E);
hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_S);
break;
case HILBERT_DIR_N:
hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_E);
hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_N);
hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_N);
hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_W);
break;
case HILBERT_DIR_S:
hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_W);
hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_S);
hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_S);
hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_E);
break;
case HILBERT_DIR_W:
hilbertWalk(id, pixData, order, nextX, nextY, HILBERT_DIR_S);
hilbertWalk(id, pixData, order, nextX, y, HILBERT_DIR_W);
hilbertWalk(id, pixData, order, x, y, HILBERT_DIR_W);
hilbertWalk(id, pixData, order, x, nextY, HILBERT_DIR_N);
break;
default:
throw new RuntimeException("Unexpected Hilbert direction " +
dir);
}
}
}
private Point hilbertOrderData(ImageData id, byte pixData[]) {
int order = 0;
for (int n = 1; n < id.width; n *= 2) {
order++;
}
/* Skanky; use an otherwise-unused ImageData field
* to keep track of maxX.
*/
Point p = new Point(0,0);
int oldIdX = id.x;
int oldIdY = id.y;
id.x = id.y = 0;
try {
hilbertWalk(id, new ByteArrayInputStream(pixData),
order, 0, 0, HILBERT_DIR_E);
p.x = id.x;
p.y = id.y;
} catch (IOException ex) {
System.err.println("Exception during hilbertWalk()");
p.x = id.height;
p.y = id.width;
}
id.x = oldIdX;
id.y = oldIdY;
return p;
}
private ImageData createHilbertHeapImage(byte pixData[]) {
int w, h;
// Pick an image size that the largest of heaps will fit into.
w = (int)Math.sqrt((double)((16 * 1024 * 1024)/8));
// Space-filling curves require a power-of-2 width.
w = nextPow2(w);
h = w;
// Create the heap image.
ImageData id = new ImageData(w, h, 8, mMapPalette);
// Copy the data into the image
//int maxX = zOrderData(id, pixData);
Point maxP = hilbertOrderData(id, pixData);
// update the max size to make it a round number once the zoom is applied
int factor = 100 / ZOOMS[mZoom.getSelectionIndex()];
if (factor != 1) {
int tmp = maxP.x % factor;
if (tmp != 0) {
maxP.x += factor - tmp;
}
tmp = maxP.y % factor;
if (tmp != 0) {
maxP.y += factor - tmp;
}
}
if (maxP.y < id.height) {
// Crop the image down to the interesting part.
id = new ImageData(id.width, maxP.y, id.depth, id.palette,
id.scanlinePad, id.data);
}
if (maxP.x < id.width) {
// crop the image again. A bit trickier this time.
ImageData croppedId = new ImageData(maxP.x, id.height, id.depth, id.palette);
int[] buffer = new int[maxP.x];
for (int l = 0 ; l < id.height; l++) {
id.getPixels(0, l, maxP.x, buffer, 0);
croppedId.setPixels(0, l, maxP.x, buffer, 0);
}
id = croppedId;
}
// apply the zoom
if (factor != 1) {
id = id.scaledTo(id.width / factor, id.height / factor);
}
return id;
}
/**
* Convert the raw heap data to an image. We know we're running in
* the UI thread, so we can issue graphics commands directly.
*
* http://help.eclipse.org/help31/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html
*
* @param cd The client data
* @param mode The display mode. 0 = linear, 1 = hilbert.
* @param forceRedraw
*/
private void renderHeapData(ClientData cd, int mode, boolean forceRedraw) {
Image image;
byte[] pixData;
// Atomically get and clear the heap data.
synchronized (cd) {
if (serializeHeapData(cd.getVmHeapData()) == false && forceRedraw == false) {
// no change, we return.
return;
}
pixData = getSerializedData();
}
if (pixData != null) {
ImageData id;
if (mode == 1) {
id = createHilbertHeapImage(pixData);
} else {
id = createLinearHeapImage(pixData, 200, mMapPalette);
}
image = new Image(mDisplay, id);
} else {
// Render a placeholder image.
int width, height;
if (mode == 1) {
width = height = PLACEHOLDER_HILBERT_SIZE;
} else {
width = PLACEHOLDER_LINEAR_H_SIZE;
height = PLACEHOLDER_LINEAR_V_SIZE;
}
image = new Image(mDisplay, width, height);
GC gc = new GC(image);
gc.setForeground(mDisplay.getSystemColor(SWT.COLOR_RED));
gc.drawLine(0, 0, width-1, height-1);
gc.dispose();
gc = null;
}
// set the new image
if (mode == 1) {
if (mHilbertImage != null) {
mHilbertImage.dispose();
}
mHilbertImage = image;
mHilbertHeapImage.setImage(mHilbertImage);
mHilbertHeapImage.pack(true);
mHilbertBase.layout();
mHilbertBase.pack(true);
} else {
if (mLinearImage != null) {
mLinearImage.dispose();
}
mLinearImage = image;
mLinearHeapImage.setImage(mLinearImage);
mLinearHeapImage.pack(true);
mLinearBase.layout();
mLinearBase.pack(true);
}
}
@Override
protected void setTableFocusListener() {
addTableToFocusListener(mHeapSummary);
}
}