1 /* @(#)TCTool.java 1.4 02/10/24 21:17:44 */ 2 package tilecachetool; 3 4 import java.util.Observer; 5 import java.util.Observable; 6 import java.util.Vector; 7 import java.awt.Color; 8 import java.awt.BorderLayout; 9 import java.awt.FlowLayout; 10 import java.awt.GridLayout; 11 import java.awt.event.ActionEvent; 12 import java.awt.event.ActionListener; 13 import java.awt.event.WindowEvent; 14 import java.awt.event.WindowListener; 15 import java.awt.event.WindowAdapter; 16 import java.awt.image.RenderedImage; 17 import javax.swing.ButtonGroup; 18 import javax.swing.JFrame; 19 import javax.swing.JButton; 20 import javax.swing.JLabel; 21 import javax.swing.JPanel; 22 import javax.swing.JMenu; 23 import javax.swing.JMenuBar; 24 import javax.swing.JMenuItem; 25 import javax.swing.JOptionPane; 26 import javax.swing.JRadioButton; 27 import javax.swing.SwingUtilities; 28 import javax.swing.UIManager; 29 import javax.swing.WindowConstants; 30 import javax.swing.border.EtchedBorder; 31 import javax.swing.border.LineBorder; 32 import com.lightcrafts.mediax.jai.JAI; 33 import com.lightcrafts.mediax.jai.CachedTile; 34 import com.lightcrafts.mediax.jai.EnumeratedParameter; 35 36 import com.lightcrafts.jai.utils.LCTileCache; 37 38 /** 39 * <p>Title: Tile Cache Monitoring Tool</p> 40 * <p>Description: Monitors and displays JAI Tile Cache activity.</p> 41 * <p>Copyright: Copyright (c) 2002</p> 42 * <p> All Rights Reserved </p> 43 * <p>Company: Virtual Visions Software, Inc.</p> 44 * 45 * @author Dennis Sigel 46 * @version 1.01 47 * 48 * NOTE: The act of observing can change the observed behavior!!! 49 * This tool will impact performance and will use memory 50 * from the JVM which interacts with the garbage collector. 51 * An attempt was made to minimize these perturbations in 52 * order to provide a useful method of monitoring and 53 * understanding the JAI tile cache. 54 */ 55 56 public final class TCTool extends JFrame 57 implements ActionListener, 58 Observer { 59 60 private LCTileCache cache = null; 61 62 private JPanel top_panel; 63 private JButton gc_btn; 64 private JButton flush_btn; 65 private JButton reset_btn; 66 private JButton quit_btn; 67 private JMenu options_menu; 68 private JMenuItem pack_item; // refit window size 69 private JMenu capacity_menu; // slider cache memory capacity (max value) 70 private JMenu delay_menu; 71 private JMenu rows_menu; 72 private JMenu laf_menu; 73 private JRadioButton rad1; 74 private JRadioButton rad2; 75 private JRadioButton rad3; 76 private JRadioButton rad4; 77 78 private static final String METAL = "javax.swing.plaf.metal.MetalLookAndFeel"; 79 private static final String WINDOWS = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; 80 private static final String MOTIF = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; 81 private static final String MAC = "com.sun.java.swing.plaf.mac.MacLookAndFeel"; 82 83 private String current_laf = METAL; 84 85 private final MemoryChart memoryChart = new MemoryChart(); 86 private final EventViewer eventViewer = new EventViewer(); 87 private final Statistics statistics = new Statistics(); 88 private final TCInfo information = new TCInfo(); 89 90 private static final Color LIGHT_BLUE = new Color(200, 200, 220); 91 private static final LineBorder LINE_BORDER = new LineBorder(Color.darkGray, 92 1); 93 94 private static final String[] capacities = { 95 "32MB", 96 "64MB", 97 "128MB", 98 "256MB", 99 "512MB" 100 }; 101 102 private static final String[] delays = { 103 "10ms", 104 "50ms", 105 "100ms", 106 "250ms", 107 "500ms", 108 "1000ms", 109 "2000ms", 110 "5000ms" 111 }; 112 113 private static final String[] rows = { 114 "1", 115 "4", 116 "7", 117 "10", 118 "15", 119 "20" 120 }; 121 122 private long tileSize = 0; 123 private long timeStamp = 0; 124 125 // cumulative statistics 126 private long addTile = 0; 127 private long removeTile = 0; 128 private long removeFlushed = 0; 129 private long removeMemoryControl = 0; 130 private long updateAddTile = 0; 131 private long updateGetTile = 0; 132 private long removeGC = 0; 133 134 // action events generated by the tile cache 135 private EnumeratedParameter[] actions; 136 private int CACHE_EVENT_ADD; 137 private int CACHE_EVENT_REMOVE; 138 private int CACHE_EVENT_REMOVE_BY_FLUSH; 139 private int CACHE_EVENT_REMOVE_BY_MEMORY_CONTROL; 140 private int CACHE_EVENT_UPDATE_FROM_ADD; 141 private int CACHE_EVENT_UPDATE_FROM_GETTILE; 142 private int CACHE_EVENT_ABOUT_TO_REMOVE_TILE; 143 private int CACHE_EVENT_REMOVE_BY_GC; 144 145 // about box message 146 private static final String about_msg = 147 "<html>" + 148 "<center>Tile Cache Tool</center" + 149 "<p><center>Version 1.01</center></p>" + 150 "<center>October 25, 2002</center>" + 151 "<p><center>Copyright (c) 2002, Virtual Visions Software, Inc.</center></p>" + 152 "<center>All Rights Reserved</center>" + 153 "<br></br>" + 154 "</html>"; 155 156 157 /** Default Constructor */ TCTool()158 public TCTool() { 159 create(-1, null); 160 } 161 162 /** Constructor with a non-default memory capacity */ TCTool(long memoryCapacity)163 public TCTool(long memoryCapacity) { 164 create(memoryCapacity, null); 165 } 166 167 /** Constructor with a custom tile cache */ TCTool(LCTileCache lcTileCache)168 public TCTool(LCTileCache lcTileCache) { 169 create(-1, lcTileCache); 170 } 171 172 // create a simple about box createAboutBox()173 private JMenuItem createAboutBox() { 174 JMenuItem about = new JMenuItem("About..."); 175 176 about.addActionListener( new ActionListener() { 177 public void actionPerformed(ActionEvent e) { 178 JOptionPane.showMessageDialog(top_panel, about_msg); 179 } 180 }); 181 182 return about; 183 } 184 185 // build the user interface create(long memoryCapacity, LCTileCache lcTileCache)186 private void create(long memoryCapacity, LCTileCache lcTileCache) { 187 setTitle("Tile Cache Tool"); 188 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 189 setBackground(LIGHT_BLUE); 190 191 top_panel = new JPanel(); 192 top_panel.setLayout( new BorderLayout() ); 193 getContentPane().add(top_panel); 194 195 // control options (part 1) 196 JMenuBar menuBar = new JMenuBar(); 197 menuBar.setLayout( new FlowLayout(FlowLayout.LEFT, 15, 5) ); 198 menuBar.setBackground(LIGHT_BLUE); 199 options_menu = new JMenu("Options"); 200 201 pack_item = new JMenuItem("Refit Window"); 202 pack_item.addActionListener(this); 203 pack_item.setToolTipText("Restore this window to original size."); 204 205 capacity_menu = new JMenu("Max Memory"); 206 for ( int i = 0; i < capacities.length; i++ ) { 207 JMenuItem item = new JMenuItem(capacities[i]); 208 item.addActionListener(this); 209 capacity_menu.add(item); 210 } 211 212 delay_menu = new JMenu("Time Delay"); 213 214 for ( int i = 0; i < delays.length; i++ ) { 215 JMenuItem item = new JMenuItem(delays[i]); 216 item.addActionListener(this); 217 delay_menu.add(item); 218 } 219 220 rows_menu = new JMenu("Event Rows"); 221 rows_menu.addActionListener(this); 222 223 for ( int i = 0; i < rows.length; i++ ) { 224 JMenuItem item = new JMenuItem(rows[i]); 225 item.addActionListener(this); 226 rows_menu.add(item); 227 } 228 229 JMenuItem tmp_item; 230 231 // user interface look and feel 232 laf_menu = new JMenu("Look & Feel"); 233 tmp_item = new JMenuItem("Metal"); 234 tmp_item.addActionListener(this); 235 laf_menu.add(tmp_item); 236 237 tmp_item = new JMenuItem("Windows"); 238 tmp_item.addActionListener(this); 239 laf_menu.add(tmp_item); 240 241 tmp_item = new JMenuItem("Motif"); 242 tmp_item.addActionListener(this); 243 laf_menu.add(tmp_item); 244 245 tmp_item = new JMenuItem("Mac"); 246 tmp_item.addActionListener(this); 247 laf_menu.add(tmp_item); 248 249 options_menu.add(pack_item); 250 options_menu.add(capacity_menu); 251 options_menu.add(delay_menu); 252 options_menu.add(rows_menu); 253 options_menu.add(laf_menu); 254 options_menu.add( createAboutBox() ); 255 256 menuBar.add(options_menu); 257 top_panel.add(menuBar, BorderLayout.NORTH); 258 259 // control options (part 2); 260 JPanel t2 = new JPanel(); 261 t2.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0)); 262 t2.setBorder(LINE_BORDER); 263 JLabel label1 = new JLabel("Diagnostics: "); 264 t2.add(label1); 265 rad1 = new JRadioButton("On", true); 266 rad2 = new JRadioButton("Off"); 267 rad1.addActionListener(this); 268 rad2.addActionListener(this); 269 t2.add(rad1); 270 t2.add(rad2); 271 272 rad1.setToolTipText("Perform diagnostics monitoring"); 273 rad2.setToolTipText("Stop diagnostics monitoring"); 274 275 ButtonGroup bg1 = new ButtonGroup(); 276 bg1.add(rad1); 277 bg1.add(rad2); 278 menuBar.add(t2); 279 280 // control options (part 3) 281 JPanel t3 = new JPanel(); 282 t3.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0)); 283 t3.setBorder(LINE_BORDER); 284 JLabel label2 = new JLabel("Cache: "); 285 t3.add(label2); 286 rad3 = new JRadioButton("Enabled", true); 287 rad4 = new JRadioButton("Disabled"); 288 rad3.addActionListener(this); 289 rad4.addActionListener(this); 290 t3.add(rad3); 291 t3.add(rad4); 292 293 // important information about JAI.enableDefaultTileCache() 294 // JAI.disableDefaultTileCache() 295 /** 296 * Calling JAI.enableDefaultTileCache() or JAI.disableDefaultTileCache() 297 * marks a change in tile cache usage. All state before the calls is 298 * maintained "as is", so if the tile cache is currently enabled, then 299 * a call is made to disable it, ops/tiles that were in the cache prior 300 * to the call will still be cachable. New tile requests will not be 301 * cached. The TCTool will still monitor items in the cache that were 302 * there prior to the "disable" call. Likewise, it the cache is currently 303 * disabled then enabled, tiles prior to the enable call will not be 304 * cached or monitored. The tile cache is flushed when a call is made 305 * to JAI.disableDefaultTileCache(), but any valid tiles that existed 306 * will be recomputed and cached again. 307 */ 308 rad3.setToolTipText("Resume normal cache operations"); 309 rad4.setToolTipText("Block new cache operations (status quo)"); 310 311 ButtonGroup bg2 = new ButtonGroup(); 312 bg2.add(rad3); 313 bg2.add(rad4); 314 menuBar.add(t3); 315 316 JPanel center_panel = new JPanel(); 317 center_panel.setLayout( new BorderLayout() ); 318 center_panel.setBackground(LIGHT_BLUE); 319 top_panel.add(center_panel, BorderLayout.CENTER); 320 321 if ( lcTileCache == null ) { 322 cache = (LCTileCache) JAI.getDefaultInstance().getTileCache(); 323 } else { 324 cache = lcTileCache; 325 } 326 327 // obtain cache event actions 328 actions = cache.getCachedTileActions(); 329 330 CACHE_EVENT_ADD = actions[0].getValue(); 331 CACHE_EVENT_REMOVE = actions[1].getValue(); 332 CACHE_EVENT_REMOVE_BY_FLUSH = actions[2].getValue(); 333 CACHE_EVENT_REMOVE_BY_MEMORY_CONTROL = actions[3].getValue(); 334 CACHE_EVENT_UPDATE_FROM_ADD = actions[4].getValue(); 335 CACHE_EVENT_UPDATE_FROM_GETTILE = actions[5].getValue(); 336 CACHE_EVENT_ABOUT_TO_REMOVE_TILE = actions[6].getValue(); 337 CACHE_EVENT_REMOVE_BY_GC = actions[7].getValue(); 338 339 if ( memoryCapacity >= 0 ) { 340 cache.setMemoryCapacity(memoryCapacity); 341 } 342 343 cache.enableDiagnostics(); 344 cache.addObserver(this); 345 346 // build a subpanel for other controls and stats 347 JPanel stat_panel = new JPanel(); 348 stat_panel.setBackground(LIGHT_BLUE); 349 stat_panel.setBorder(new EtchedBorder(EtchedBorder.LOWERED)); 350 stat_panel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 2)); 351 352 // buttons 353 gc_btn = new JButton("Force GC"); 354 flush_btn = new JButton("Flush Cache"); 355 reset_btn = new JButton("Reset"); 356 quit_btn = new JButton("Quit"); 357 358 gc_btn.addActionListener(this); 359 flush_btn.addActionListener(this); 360 reset_btn.addActionListener(this); 361 quit_btn.addActionListener(this); 362 363 gc_btn.setToolTipText("Force a call to System.gc()"); 364 flush_btn.setToolTipText("Empty the tile cache."); 365 reset_btn.setToolTipText("Clear counters and reset."); 366 quit_btn.setToolTipText("Close the Tile Cache Tool window."); 367 368 JPanel p1 = new JPanel(); 369 p1.setLayout(new GridLayout(4, 1, 5, 15)); 370 p1.setBackground(LIGHT_BLUE); 371 p1.add(gc_btn); 372 p1.add(flush_btn); 373 p1.add(reset_btn); 374 p1.add(quit_btn); 375 stat_panel.add(p1); 376 377 // Tile Cache Options 378 information.setTileCache(cache); 379 information.setBackground(LIGHT_BLUE); 380 information.setStatistics(statistics); 381 stat_panel.add(information); 382 center_panel.add(stat_panel, BorderLayout.NORTH); 383 384 // Tile Cache Statistics 385 statistics.setBackground(LIGHT_BLUE); 386 stat_panel.add(statistics); 387 388 // JVM memory monitor 389 center_panel.add(memoryChart, BorderLayout.CENTER); 390 memoryChart.start(); 391 392 // JAI event logging and monitoring 393 JPanel p0 = new JPanel(); 394 p0.setBackground(LIGHT_BLUE); 395 p0.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5)); 396 p0.add(eventViewer); 397 center_panel.add(p0, BorderLayout.SOUTH); 398 399 final TCTool tctool = this; 400 401 WindowListener wl = new WindowAdapter() { 402 public void windowClosing(WindowEvent e) { 403 memoryChart.stop(); 404 405 cache.deleteObserver(tctool); 406 cache.disableDiagnostics(); 407 408 cache = null; 409 System.gc(); 410 } 411 412 public void windowIconified(WindowEvent e) { 413 memoryChart.stop(); 414 } 415 416 public void windowDeiconified(WindowEvent e) { 417 memoryChart.start(); 418 } 419 }; 420 421 addWindowListener(wl); 422 423 pack(); 424 setVisible(true); 425 } 426 actionPerformed(ActionEvent e)427 public void actionPerformed(ActionEvent e) { 428 Object tmp = e.getSource(); 429 430 if ( tmp instanceof JRadioButton ) { 431 JRadioButton rb = (JRadioButton) tmp; 432 433 if ( rb.isSelected() ) { 434 if ( rb == rad1 ) { 435 if ( cache != null ) { 436 cache.enableDiagnostics(); 437 memoryChart.start(); 438 } 439 } else if ( rb == rad2 ) { 440 if ( cache != null ) { 441 cache.disableDiagnostics(); 442 memoryChart.stop(); 443 } 444 } else if ( rb == rad3 ) { 445 JAI.enableDefaultTileCache(); 446 } else if ( rb == rad4 ) { 447 JAI.disableDefaultTileCache(); 448 } 449 } 450 } 451 452 if ( tmp instanceof JButton ) { 453 JButton button = (JButton) tmp; 454 455 if ( button == gc_btn ) { 456 System.gc(); 457 } else if ( button == flush_btn ) { 458 if ( cache != null ) { 459 cache.flush(); 460 } 461 } else if ( button == reset_btn ) { 462 if ( cache != null ) { 463 cache.flush(); 464 } 465 466 // clear cumulative values 467 addTile = 0; 468 removeTile = 0; 469 removeFlushed = 0; 470 removeMemoryControl = 0; 471 updateAddTile = 0; 472 updateGetTile = 0; 473 474 SwingUtilities.invokeLater(new Runnable() { 475 public void run() { 476 statistics.clear(); 477 eventViewer.clear(); 478 } 479 }); 480 481 System.gc(); //too early 482 } else if ( button == quit_btn ) { 483 memoryChart.stop(); 484 cache.deleteObserver(this); 485 cache.disableDiagnostics(); 486 cache = null; 487 488 dispose(); 489 System.gc(); 490 } 491 } 492 493 if ( tmp instanceof JMenuItem ) { 494 JMenuItem item = (JMenuItem) tmp; 495 496 if ( item == pack_item ) { 497 pack(); 498 } else { 499 String cmd = item.getText(); 500 501 // check look and feel options 502 if ( cmd.equalsIgnoreCase("Metal") ) { 503 setLookAndFeel(METAL); 504 return; 505 } else if ( cmd.equalsIgnoreCase("Windows") ) { 506 setLookAndFeel(WINDOWS); 507 return; 508 } else if ( cmd.equalsIgnoreCase("Motif") ) { 509 setLookAndFeel(MOTIF); 510 return; 511 } else if ( cmd.equalsIgnoreCase("Mac") ) { 512 setLookAndFeel(MAC); 513 return; 514 } 515 516 // memory capacity range for slider 517 for ( int i = 0; i < capacities.length; i++ ) { 518 if ( cmd.equals(capacities[i]) ) { 519 String str = capacities[i].substring(0, capacities[i].lastIndexOf("M")); 520 int mem_max = Integer.parseInt(str); 521 information.setMemoryCapacitySliderMaximum( mem_max ); 522 return; 523 } 524 } 525 526 // memory chart speed 527 for ( int i = 0; i < delays.length; i++ ) { 528 if ( cmd.equals(delays[i]) ) { 529 String str = delays[i].substring(0, delays[i].lastIndexOf("m")); 530 memoryChart.setDelay( Integer.parseInt(str) ); 531 return; 532 } 533 } 534 535 // number of rows in the event viewer 536 for ( int i = 0; i < rows.length; i++ ) { 537 if ( cmd.equals(rows[i]) ) { 538 eventViewer.setRows( Integer.parseInt(rows[i]) ); 539 pack(); 540 return; 541 } 542 } 543 } 544 } 545 } 546 setLookAndFeel(String laf)547 private void setLookAndFeel(String laf) { 548 if ( current_laf != laf ) { 549 current_laf = laf; 550 551 try { 552 UIManager.setLookAndFeel(current_laf); 553 SwingUtilities.updateComponentTreeUI(this); 554 pack(); 555 } catch( Exception e ) { 556 System.out.println("Look and Feel not supported."); 557 current_laf = METAL; 558 } 559 } 560 } 561 update(Observable possibleSunTileCache, Object possibleCachedTile)562 public synchronized void update(Observable possibleSunTileCache, 563 Object possibleCachedTile) { 564 565 LCTileCache lc_tile_cache = null; 566 CachedTile cached_tile = null; 567 int cache_event = -1; 568 569 // check SunTileCache 570 if ( possibleSunTileCache instanceof LCTileCache ) { 571 lc_tile_cache = (LCTileCache) possibleSunTileCache; 572 } else { 573 return; 574 } 575 576 // check cached tile 577 if ( (possibleCachedTile != null) && 578 (possibleCachedTile instanceof CachedTile) ) { 579 cached_tile = (CachedTile) possibleCachedTile; 580 cache_event = cached_tile.getAction(); 581 582 if ( cache_event == CACHE_EVENT_ABOUT_TO_REMOVE_TILE ) { 583 return; 584 } 585 } else { 586 return; 587 } 588 589 // collect information 590 RenderedImage image = (RenderedImage) cached_tile.getOwner(); 591 592 final long tileSize = cached_tile.getTileSize(); 593 final long timeStamp = cached_tile.getTileTimeStamp(); 594 final long cacheHits = lc_tile_cache.getCacheHitCount(); 595 final long cacheMisses = lc_tile_cache.getCacheMissCount(); 596 final long tileCount = lc_tile_cache.getCacheTileCount(); 597 598 float memoryCapacity = (float) lc_tile_cache.getMemoryCapacity(); 599 float memoryUsage = (float) lc_tile_cache.getCacheMemoryUsed(); 600 final int percentTCM = (int) ((100.0F * memoryUsage / memoryCapacity) + 0.5F); 601 602 String jai_op; 603 604 // image can be null if it was garbage collected 605 if ( image != null ) { 606 String temp = image.getClass().getName(); 607 jai_op = temp.substring(temp.lastIndexOf(".") + 1); 608 } else { 609 jai_op = "Op was removed by GC"; 610 } 611 612 final Vector eventData = new Vector(5,1); 613 eventData.addElement(jai_op); 614 615 if ( cache_event == CACHE_EVENT_ADD ) { 616 eventData.addElement("Add"); 617 addTile++; 618 } else if ( cache_event == CACHE_EVENT_REMOVE ) { 619 eventData.addElement("Remove"); 620 removeTile++; 621 } else if ( cache_event == CACHE_EVENT_REMOVE_BY_FLUSH ) { 622 eventData.addElement("Remove by Flush"); 623 removeFlushed++; 624 } else if ( cache_event == CACHE_EVENT_REMOVE_BY_MEMORY_CONTROL ) { 625 eventData.addElement("Remove by Memory Control"); 626 removeMemoryControl++; 627 } else if ( cache_event == CACHE_EVENT_UPDATE_FROM_ADD ) { 628 eventData.addElement("Update from Add"); 629 updateAddTile++; 630 } else if ( cache_event == CACHE_EVENT_UPDATE_FROM_GETTILE ) { 631 eventData.addElement("Update from GetTile"); 632 updateGetTile++; 633 } else if ( cache_event == CACHE_EVENT_REMOVE_BY_GC ) { 634 eventData.addElement("Remove by Memory Control"); 635 removeGC++; 636 } 637 638 eventData.addElement("" + tileSize); 639 eventData.addElement("" + timeStamp); 640 641 /* synchronizes data fields */ 642 final long f_addTile = addTile; 643 final long f_removeTile = removeTile; 644 final long f_removeFlushed = removeFlushed; 645 final long f_removeMemoryControl = removeMemoryControl; 646 final long f_removeGC = removeGC; 647 final long f_updateAddTile = updateAddTile; 648 final long f_updateGetTile = updateGetTile; 649 650 /** 651 * Bug 0001 reported and suggested fix by Mike Pilone 652 */ 653 654 /* fixes hang in Swing applications */ 655 SwingUtilities.invokeLater(new Runnable() { 656 public void run() { 657 eventViewer.insertRow(0, eventData); 658 659 statistics.set(tileCount, 660 cacheHits, 661 cacheMisses, 662 f_addTile, 663 f_removeTile, 664 f_removeFlushed, 665 f_removeMemoryControl, 666 f_removeGC, 667 f_updateAddTile, 668 f_updateGetTile, 669 percentTCM); 670 } 671 }); 672 } 673 } 674