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