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 */ createPalette()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 */ clientChanged(final Client client, int changeMask)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 deviceSelected()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 clientSelected()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 createControl(Composite parent)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(ImageHelper.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(ImageHelper.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 setFocus()489 public void setFocus() { 490 mHeapSummary.setFocus(); 491 } 492 493 createSummaryTable(Composite base)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 createDetailedTable(Composite base)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 */ createChart()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 prettyByteCount(long bytes)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 approximateByteCount(long bytes)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 addCommasToNumber(long num)695 private static String addCommasToNumber(long num) { 696 return sCountFormatter.format(num); 697 } 698 fractionalPercent(long num, long denom)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 fillSummaryTable(ClientData cd)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 fillDetailedTable(Client client, boolean forceRedraw)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 ByteLong(long value)849 private ByteLong(long value) { 850 mValue = value; 851 } 852 getValue()853 public long getValue() { 854 return mValue; 855 } 856 857 @Override toString()858 public String toString() { 859 return approximateByteCount(mValue); 860 } 861 compareTo(ByteLong other)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 */ showChart(ArrayList<HeapSegmentElement> list)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 */ createLegend(Composite parent)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 getLegendText(int level)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 setLegendText(int level)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 */ createColorRect(Display display, RGB color)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 */ setUpdateStatus(int status)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(). nextPow2(int value)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 zOrderData(ImageData id, byte pixData[])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 hilbertWalk(ImageData id, InputStream pixData, int order, int x, int y, int dir)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 hilbertOrderData(ImageData id, byte pixData[])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 createHilbertHeapImage(byte pixData[])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 */ renderHeapData(ClientData cd, int mode, boolean forceRedraw)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 setTableFocusListener()1290 protected void setTableFocusListener() { 1291 addTableToFocusListener(mHeapSummary); 1292 } 1293 } 1294 1295