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