/* @(#)TCTool.java 1.4 02/10/24 21:17:44 */
package tilecachetool;
import java.util.Observer;
import java.util.Observable;
import java.util.Vector;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.event.WindowAdapter;
import java.awt.image.RenderedImage;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.border.EtchedBorder;
import javax.swing.border.LineBorder;
import com.lightcrafts.mediax.jai.JAI;
import com.lightcrafts.mediax.jai.CachedTile;
import com.lightcrafts.mediax.jai.EnumeratedParameter;
import com.lightcrafts.jai.utils.LCTileCache;
/**
* <p>Title: Tile Cache Monitoring Tool</p>
* <p>Description: Monitors and displays JAI Tile Cache activity.</p>
* <p>Copyright: Copyright (c) 2002</p>
* <p> All Rights Reserved </p>
* <p>Company: Virtual Visions Software, Inc.</p>
*
* @author Dennis Sigel
* @version 1.01
*
* NOTE: The act of observing can change the observed behavior!!!
* This tool will impact performance and will use memory
* from the JVM which interacts with the garbage collector.
* An attempt was made to minimize these perturbations in
* order to provide a useful method of monitoring and
* understanding the JAI tile cache.
*/
public final class TCTool extends JFrame
implements ActionListener,
Observer {
private LCTileCache cache = null;
private JPanel top_panel;
private JButton gc_btn;
private JButton flush_btn;
private JButton reset_btn;
private JButton quit_btn;
private JMenu options_menu;
private JMenuItem pack_item; // refit window size
private JMenu capacity_menu; // slider cache memory capacity (max value)
private JMenu delay_menu;
private JMenu rows_menu;
private JMenu laf_menu;
private JRadioButton rad1;
private JRadioButton rad2;
private JRadioButton rad3;
private JRadioButton rad4;
private static final String METAL = "javax.swing.plaf.metal.MetalLookAndFeel";
private static final String WINDOWS = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
private static final String MOTIF = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
private static final String MAC = "com.sun.java.swing.plaf.mac.MacLookAndFeel";
private String current_laf = METAL;
private final MemoryChart memoryChart = new MemoryChart();
private final EventViewer eventViewer = new EventViewer();
private final Statistics statistics = new Statistics();
private final TCInfo information = new TCInfo();
private static final Color LIGHT_BLUE = new Color(200, 200, 220);
private static final LineBorder LINE_BORDER = new LineBorder(Color.darkGray,
1);
private static final String[] capacities = {
"32MB",
"64MB",
"128MB",
"256MB",
"512MB"
};
private static final String[] delays = {
"10ms",
"50ms",
"100ms",
"250ms",
"500ms",
"1000ms",
"2000ms",
"5000ms"
};
private static final String[] rows = {
"1",
"4",
"7",
"10",
"15",
"20"
};
private long tileSize = 0;
private long timeStamp = 0;
// cumulative statistics
private long addTile = 0;
private long removeTile = 0;
private long removeFlushed = 0;
private long removeMemoryControl = 0;
private long updateAddTile = 0;
private long updateGetTile = 0;
private long removeGC = 0;
// action events generated by the tile cache
private EnumeratedParameter[] actions;
private int CACHE_EVENT_ADD;
private int CACHE_EVENT_REMOVE;
private int CACHE_EVENT_REMOVE_BY_FLUSH;
private int CACHE_EVENT_REMOVE_BY_MEMORY_CONTROL;
private int CACHE_EVENT_UPDATE_FROM_ADD;
private int CACHE_EVENT_UPDATE_FROM_GETTILE;
private int CACHE_EVENT_ABOUT_TO_REMOVE_TILE;
private int CACHE_EVENT_REMOVE_BY_GC;
// about box message
private static final String about_msg =
"<html>" +
"<center>Tile Cache Tool</center" +
"<p><center>Version 1.01</center></p>" +
"<center>October 25, 2002</center>" +
"<p><center>Copyright (c) 2002, Virtual Visions Software, Inc.</center></p>" +
"<center>All Rights Reserved</center>" +
"<br></br>" +
"</html>";
/** Default Constructor */
public TCTool() {
create(-1, null);
}
/** Constructor with a non-default memory capacity */
public TCTool(long memoryCapacity) {
create(memoryCapacity, null);
}
/** Constructor with a custom tile cache */
public TCTool(LCTileCache lcTileCache) {
create(-1, lcTileCache);
}
// create a simple about box
private JMenuItem createAboutBox() {
JMenuItem about = new JMenuItem("About...");
about.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(top_panel, about_msg);
}
});
return about;
}
// build the user interface
private void create(long memoryCapacity, LCTileCache lcTileCache) {
setTitle("Tile Cache Tool");
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setBackground(LIGHT_BLUE);
top_panel = new JPanel();
top_panel.setLayout( new BorderLayout() );
getContentPane().add(top_panel);
// control options (part 1)
JMenuBar menuBar = new JMenuBar();
menuBar.setLayout( new FlowLayout(FlowLayout.LEFT, 15, 5) );
menuBar.setBackground(LIGHT_BLUE);
options_menu = new JMenu("Options");
pack_item = new JMenuItem("Refit Window");
pack_item.addActionListener(this);
pack_item.setToolTipText("Restore this window to original size.");
capacity_menu = new JMenu("Max Memory");
for ( int i = 0; i < capacities.length; i++ ) {
JMenuItem item = new JMenuItem(capacities[i]);
item.addActionListener(this);
capacity_menu.add(item);
}
delay_menu = new JMenu("Time Delay");
for ( int i = 0; i < delays.length; i++ ) {
JMenuItem item = new JMenuItem(delays[i]);
item.addActionListener(this);
delay_menu.add(item);
}
rows_menu = new JMenu("Event Rows");
rows_menu.addActionListener(this);
for ( int i = 0; i < rows.length; i++ ) {
JMenuItem item = new JMenuItem(rows[i]);
item.addActionListener(this);
rows_menu.add(item);
}
JMenuItem tmp_item;
// user interface look and feel
laf_menu = new JMenu("Look & Feel");
tmp_item = new JMenuItem("Metal");
tmp_item.addActionListener(this);
laf_menu.add(tmp_item);
tmp_item = new JMenuItem("Windows");
tmp_item.addActionListener(this);
laf_menu.add(tmp_item);
tmp_item = new JMenuItem("Motif");
tmp_item.addActionListener(this);
laf_menu.add(tmp_item);
tmp_item = new JMenuItem("Mac");
tmp_item.addActionListener(this);
laf_menu.add(tmp_item);
options_menu.add(pack_item);
options_menu.add(capacity_menu);
options_menu.add(delay_menu);
options_menu.add(rows_menu);
options_menu.add(laf_menu);
options_menu.add( createAboutBox() );
menuBar.add(options_menu);
top_panel.add(menuBar, BorderLayout.NORTH);
// control options (part 2);
JPanel t2 = new JPanel();
t2.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));
t2.setBorder(LINE_BORDER);
JLabel label1 = new JLabel("Diagnostics: ");
t2.add(label1);
rad1 = new JRadioButton("On", true);
rad2 = new JRadioButton("Off");
rad1.addActionListener(this);
rad2.addActionListener(this);
t2.add(rad1);
t2.add(rad2);
rad1.setToolTipText("Perform diagnostics monitoring");
rad2.setToolTipText("Stop diagnostics monitoring");
ButtonGroup bg1 = new ButtonGroup();
bg1.add(rad1);
bg1.add(rad2);
menuBar.add(t2);
// control options (part 3)
JPanel t3 = new JPanel();
t3.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));
t3.setBorder(LINE_BORDER);
JLabel label2 = new JLabel("Cache: ");
t3.add(label2);
rad3 = new JRadioButton("Enabled", true);
rad4 = new JRadioButton("Disabled");
rad3.addActionListener(this);
rad4.addActionListener(this);
t3.add(rad3);
t3.add(rad4);
// important information about JAI.enableDefaultTileCache()
// JAI.disableDefaultTileCache()
/**
* Calling JAI.enableDefaultTileCache() or JAI.disableDefaultTileCache()
* marks a change in tile cache usage. All state before the calls is
* maintained "as is", so if the tile cache is currently enabled, then
* a call is made to disable it, ops/tiles that were in the cache prior
* to the call will still be cachable. New tile requests will not be
* cached. The TCTool will still monitor items in the cache that were
* there prior to the "disable" call. Likewise, it the cache is currently
* disabled then enabled, tiles prior to the enable call will not be
* cached or monitored. The tile cache is flushed when a call is made
* to JAI.disableDefaultTileCache(), but any valid tiles that existed
* will be recomputed and cached again.
*/
rad3.setToolTipText("Resume normal cache operations");
rad4.setToolTipText("Block new cache operations (status quo)");
ButtonGroup bg2 = new ButtonGroup();
bg2.add(rad3);
bg2.add(rad4);
menuBar.add(t3);
JPanel center_panel = new JPanel();
center_panel.setLayout( new BorderLayout() );
center_panel.setBackground(LIGHT_BLUE);
top_panel.add(center_panel, BorderLayout.CENTER);
if ( lcTileCache == null ) {
cache = (LCTileCache) JAI.getDefaultInstance().getTileCache();
} else {
cache = lcTileCache;
}
// obtain cache event actions
actions = cache.getCachedTileActions();
CACHE_EVENT_ADD = actions[0].getValue();
CACHE_EVENT_REMOVE = actions[1].getValue();
CACHE_EVENT_REMOVE_BY_FLUSH = actions[2].getValue();
CACHE_EVENT_REMOVE_BY_MEMORY_CONTROL = actions[3].getValue();
CACHE_EVENT_UPDATE_FROM_ADD = actions[4].getValue();
CACHE_EVENT_UPDATE_FROM_GETTILE = actions[5].getValue();
CACHE_EVENT_ABOUT_TO_REMOVE_TILE = actions[6].getValue();
CACHE_EVENT_REMOVE_BY_GC = actions[7].getValue();
if ( memoryCapacity >= 0 ) {
cache.setMemoryCapacity(memoryCapacity);
}
cache.enableDiagnostics();
cache.addObserver(this);
// build a subpanel for other controls and stats
JPanel stat_panel = new JPanel();
stat_panel.setBackground(LIGHT_BLUE);
stat_panel.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
stat_panel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 2));
// buttons
gc_btn = new JButton("Force GC");
flush_btn = new JButton("Flush Cache");
reset_btn = new JButton("Reset");
quit_btn = new JButton("Quit");
gc_btn.addActionListener(this);
flush_btn.addActionListener(this);
reset_btn.addActionListener(this);
quit_btn.addActionListener(this);
gc_btn.setToolTipText("Force a call to System.gc()");
flush_btn.setToolTipText("Empty the tile cache.");
reset_btn.setToolTipText("Clear counters and reset.");
quit_btn.setToolTipText("Close the Tile Cache Tool window.");
JPanel p1 = new JPanel();
p1.setLayout(new GridLayout(4, 1, 5, 15));
p1.setBackground(LIGHT_BLUE);
p1.add(gc_btn);
p1.add(flush_btn);
p1.add(reset_btn);
p1.add(quit_btn);
stat_panel.add(p1);
// Tile Cache Options
information.setTileCache(cache);
information.setBackground(LIGHT_BLUE);
information.setStatistics(statistics);
stat_panel.add(information);
center_panel.add(stat_panel, BorderLayout.NORTH);
// Tile Cache Statistics
statistics.setBackground(LIGHT_BLUE);
stat_panel.add(statistics);
// JVM memory monitor
center_panel.add(memoryChart, BorderLayout.CENTER);
memoryChart.start();
// JAI event logging and monitoring
JPanel p0 = new JPanel();
p0.setBackground(LIGHT_BLUE);
p0.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
p0.add(eventViewer);
center_panel.add(p0, BorderLayout.SOUTH);
final TCTool tctool = this;
WindowListener wl = new WindowAdapter() {
public void windowClosing(WindowEvent e) {
memoryChart.stop();
cache.deleteObserver(tctool);
cache.disableDiagnostics();
cache = null;
System.gc();
}
public void windowIconified(WindowEvent e) {
memoryChart.stop();
}
public void windowDeiconified(WindowEvent e) {
memoryChart.start();
}
};
addWindowListener(wl);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
Object tmp = e.getSource();
if ( tmp instanceof JRadioButton ) {
JRadioButton rb = (JRadioButton) tmp;
if ( rb.isSelected() ) {
if ( rb == rad1 ) {
if ( cache != null ) {
cache.enableDiagnostics();
memoryChart.start();
}
} else if ( rb == rad2 ) {
if ( cache != null ) {
cache.disableDiagnostics();
memoryChart.stop();
}
} else if ( rb == rad3 ) {
JAI.enableDefaultTileCache();
} else if ( rb == rad4 ) {
JAI.disableDefaultTileCache();
}
}
}
if ( tmp instanceof JButton ) {
JButton button = (JButton) tmp;
if ( button == gc_btn ) {
System.gc();
} else if ( button == flush_btn ) {
if ( cache != null ) {
cache.flush();
}
} else if ( button == reset_btn ) {
if ( cache != null ) {
cache.flush();
}
// clear cumulative values
addTile = 0;
removeTile = 0;
removeFlushed = 0;
removeMemoryControl = 0;
updateAddTile = 0;
updateGetTile = 0;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
statistics.clear();
eventViewer.clear();
}
});
System.gc(); //too early
} else if ( button == quit_btn ) {
memoryChart.stop();
cache.deleteObserver(this);
cache.disableDiagnostics();
cache = null;
dispose();
System.gc();
}
}
if ( tmp instanceof JMenuItem ) {
JMenuItem item = (JMenuItem) tmp;
if ( item == pack_item ) {
pack();
} else {
String cmd = item.getText();
// check look and feel options
if ( cmd.equalsIgnoreCase("Metal") ) {
setLookAndFeel(METAL);
return;
} else if ( cmd.equalsIgnoreCase("Windows") ) {
setLookAndFeel(WINDOWS);
return;
} else if ( cmd.equalsIgnoreCase("Motif") ) {
setLookAndFeel(MOTIF);
return;
} else if ( cmd.equalsIgnoreCase("Mac") ) {
setLookAndFeel(MAC);
return;
}
// memory capacity range for slider
for ( int i = 0; i < capacities.length; i++ ) {
if ( cmd.equals(capacities[i]) ) {
String str = capacities[i].substring(0, capacities[i].lastIndexOf("M"));
int mem_max = Integer.parseInt(str);
information.setMemoryCapacitySliderMaximum( mem_max );
return;
}
}
// memory chart speed
for ( int i = 0; i < delays.length; i++ ) {
if ( cmd.equals(delays[i]) ) {
String str = delays[i].substring(0, delays[i].lastIndexOf("m"));
memoryChart.setDelay( Integer.parseInt(str) );
return;
}
}
// number of rows in the event viewer
for ( int i = 0; i < rows.length; i++ ) {
if ( cmd.equals(rows[i]) ) {
eventViewer.setRows( Integer.parseInt(rows[i]) );
pack();
return;
}
}
}
}
}
private void setLookAndFeel(String laf) {
if ( current_laf != laf ) {
current_laf = laf;
try {
UIManager.setLookAndFeel(current_laf);
SwingUtilities.updateComponentTreeUI(this);
pack();
} catch( Exception e ) {
System.out.println("Look and Feel not supported.");
current_laf = METAL;
}
}
}
public synchronized void update(Observable possibleSunTileCache,
Object possibleCachedTile) {
LCTileCache lc_tile_cache = null;
CachedTile cached_tile = null;
int cache_event = -1;
// check SunTileCache
if ( possibleSunTileCache instanceof LCTileCache ) {
lc_tile_cache = (LCTileCache) possibleSunTileCache;
} else {
return;
}
// check cached tile
if ( (possibleCachedTile != null) &&
(possibleCachedTile instanceof CachedTile) ) {
cached_tile = (CachedTile) possibleCachedTile;
cache_event = cached_tile.getAction();
if ( cache_event == CACHE_EVENT_ABOUT_TO_REMOVE_TILE ) {
return;
}
} else {
return;
}
// collect information
RenderedImage image = (RenderedImage) cached_tile.getOwner();
final long tileSize = cached_tile.getTileSize();
final long timeStamp = cached_tile.getTileTimeStamp();
final long cacheHits = lc_tile_cache.getCacheHitCount();
final long cacheMisses = lc_tile_cache.getCacheMissCount();
final long tileCount = lc_tile_cache.getCacheTileCount();
float memoryCapacity = (float) lc_tile_cache.getMemoryCapacity();
float memoryUsage = (float) lc_tile_cache.getCacheMemoryUsed();
final int percentTCM = (int) ((100.0F * memoryUsage / memoryCapacity) + 0.5F);
String jai_op;
// image can be null if it was garbage collected
if ( image != null ) {
String temp = image.getClass().getName();
jai_op = temp.substring(temp.lastIndexOf(".") + 1);
} else {
jai_op = "Op was removed by GC";
}
final Vector eventData = new Vector(5,1);
eventData.addElement(jai_op);
if ( cache_event == CACHE_EVENT_ADD ) {
eventData.addElement("Add");
addTile++;
} else if ( cache_event == CACHE_EVENT_REMOVE ) {
eventData.addElement("Remove");
removeTile++;
} else if ( cache_event == CACHE_EVENT_REMOVE_BY_FLUSH ) {
eventData.addElement("Remove by Flush");
removeFlushed++;
} else if ( cache_event == CACHE_EVENT_REMOVE_BY_MEMORY_CONTROL ) {
eventData.addElement("Remove by Memory Control");
removeMemoryControl++;
} else if ( cache_event == CACHE_EVENT_UPDATE_FROM_ADD ) {
eventData.addElement("Update from Add");
updateAddTile++;
} else if ( cache_event == CACHE_EVENT_UPDATE_FROM_GETTILE ) {
eventData.addElement("Update from GetTile");
updateGetTile++;
} else if ( cache_event == CACHE_EVENT_REMOVE_BY_GC ) {
eventData.addElement("Remove by Memory Control");
removeGC++;
}
eventData.addElement("" + tileSize);
eventData.addElement("" + timeStamp);
/* synchronizes data fields */
final long f_addTile = addTile;
final long f_removeTile = removeTile;
final long f_removeFlushed = removeFlushed;
final long f_removeMemoryControl = removeMemoryControl;
final long f_removeGC = removeGC;
final long f_updateAddTile = updateAddTile;
final long f_updateGetTile = updateGetTile;
/**
* Bug 0001 reported and suggested fix by Mike Pilone
*/
/* fixes hang in Swing applications */
SwingUtilities.invokeLater(new Runnable() {
public void run() {
eventViewer.insertRow(0, eventData);
statistics.set(tileCount,
cacheHits,
cacheMisses,
f_addTile,
f_removeTile,
f_removeFlushed,
f_removeMemoryControl,
f_removeGC,
f_updateAddTile,
f_updateGetTile,
percentTCM);
}
});
}
}