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