001    /*
002     * $Id: MemoryMonitor.java,v 1.13 2012/02/19 17:35:52 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv.util;
032    
033    import static javax.swing.GroupLayout.DEFAULT_SIZE;
034    import static javax.swing.GroupLayout.Alignment.LEADING;
035    
036    import java.awt.BorderLayout;
037    import java.awt.Color;
038    import java.awt.Font;
039    import java.awt.event.MouseAdapter;
040    import java.awt.event.MouseEvent;
041    import java.awt.event.MouseListener;
042    import java.text.DecimalFormat;
043    import java.text.SimpleDateFormat;
044    import java.util.Date;
045    
046    import javax.swing.GroupLayout;
047    import javax.swing.JFrame;
048    import javax.swing.JLabel;
049    import javax.swing.JPanel;
050    import javax.swing.JPopupMenu;
051    import javax.swing.SwingUtilities;
052    
053    import ucar.unidata.idv.IntegratedDataViewer;
054    import ucar.unidata.util.CacheManager;
055    import ucar.unidata.util.GuiUtils;
056    import ucar.unidata.util.Msg;
057    
058    // initial version taken verbatim from Unidata :(
059    public class MemoryMonitor extends JPanel implements Runnable {
060    
061        /** flag for running */
062        private boolean running = false;
063    
064        /** sleep interval */
065        private final long sleepInterval = 2000;
066    
067        /** a thread */
068        private Thread thread;
069    
070        /** percent threshold */
071        private final int percentThreshold;
072    
073        /** number of times above the threshold */
074        private int timesAboveThreshold = 0;
075        
076        /** percent cancel */
077        private final int percentCancel;
078        
079        /** have we tried to cancel the load yet */
080        private boolean triedToCancel = false;
081    
082        /** format */
083        private static DecimalFormat fmt = new DecimalFormat("#0");
084    
085        /** the label */
086        private JLabel label = new JLabel("");
087        
088        /** Keep track of the last time we ran the gc and cleared the cache */
089        private static long lastTimeRanGC = -1;
090        
091        /** Keep track of the IDV so we can try to cancel loads if mem usage gets high */
092        private IntegratedDataViewer idv;
093    
094        private boolean showClock = false;
095    
096        private static final Font clockFont = new Font("Dialog", Font.BOLD, 11);
097    
098        private static SimpleDateFormat clockFormat = new SimpleDateFormat("HH:mm:ss z");
099    
100        /**
101         * Default constructor
102         */
103        public MemoryMonitor(IntegratedDataViewer idv) {
104            this(idv, 75, 95, false);
105        }
106    
107        /**
108         * Create a new MemoryMonitor
109         * 
110         * @param percentThreshold the percentage of use memory before garbage
111         *        collection is run
112         * 
113         */
114        public MemoryMonitor(IntegratedDataViewer idv, final int percentThreshold, final int percentCancel, boolean showClock) {
115            super(new BorderLayout());
116            this.idv = idv;
117            this.showClock = showClock;
118            Font f = label.getFont();
119            label.setToolTipText("Used memory/Max used memory/Max memory");
120            label.setFont(f);
121            this.percentThreshold = percentThreshold;
122            this.percentCancel = percentCancel;
123    
124            GroupLayout layout = new GroupLayout(this);
125            this.setLayout(layout);
126            layout.setHorizontalGroup(
127                layout.createParallelGroup(LEADING)
128                .addComponent(label, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
129            );
130            layout.setVerticalGroup(
131                layout.createParallelGroup(LEADING)
132                .addComponent(label, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
133            );
134    
135    //        MouseListener ml = new MouseAdapter() {
136    //            @Override public void mouseClicked(MouseEvent e) {
137    //                if (SwingUtilities.isRightMouseButton(e))
138    //                    popupMenu(e);
139    //            }
140    //        };
141            MouseListener ml = new MouseAdapter() {
142                public void mouseClicked(MouseEvent e) {
143                    if (!SwingUtilities.isRightMouseButton(e)) {
144                        toggleClock();
145                        showStats();
146                    }
147                    handleMouseEvent(e);
148                }
149            };
150    
151            label.addMouseListener(ml);
152            label.setOpaque(true);
153            label.setBackground(doColorThing(0));
154            start();
155        }
156    
157        /**
158         * Handle a mouse event
159         *
160         * @param event the event
161         */
162        private void handleMouseEvent(MouseEvent event) {
163            if (SwingUtilities.isRightMouseButton(event)) {
164                popupMenu(event);
165                return;
166            }
167        }
168    
169        private void toggleClock() {
170            this.showClock = !this.showClock;
171            idv.getStateManager().putPreference("idv.monitor.showclock", this.showClock);
172        }
173    
174        private String getToolTip() {
175            if (showClock) {
176                return "Current time";
177            } else {
178                return "Used memory/Max used memory/Max memory";
179            }
180        }
181    
182        /**
183         * Popup a menu on an event
184         * 
185         * @param event the event
186         */
187        private void popupMenu(final MouseEvent event) {
188            JPopupMenu popup = new JPopupMenu();
189    //        if (running) {
190    //            popup.add(GuiUtils.makeMenuItem("Stop Running",
191    //                MemoryMonitor.this, "toggleRunning"));
192    //        } else {
193    //            popup.add(GuiUtils.makeMenuItem("Resume Running",
194    //                MemoryMonitor.this, "toggleRunning"));
195    //        }
196    
197            popup.add(GuiUtils.makeMenuItem("Clear Memory & Cache",
198                MemoryMonitor.this, "runGC"));
199            popup.show(this, event.getX(), event.getY());
200        }
201    
202        /**
203         * Toggle running
204         */
205        public void toggleRunning() {
206            if (running) {
207                stop();
208            } else {
209                start();
210            }
211        }
212    
213        /**
214         * Set the label font
215         * 
216         * @param f the font
217         */
218        public void setLabelFont(final Font f) {
219            label.setFont(f);
220        }
221    
222        /**
223         * Stop running
224         */
225        public synchronized void stop() {
226            running = false;
227            label.setEnabled(false);
228        }
229    
230        /**
231         * Start running
232         */
233        private synchronized void start() {
234            if (running)
235                return;
236    
237            label.setEnabled(true);
238            running = true;
239            triedToCancel = false;
240            thread = new Thread(this, "Memory monitor");
241            thread.start();
242        }
243    
244        /**
245         * Run the GC and clear the cache
246         */
247        public void runGC() {
248            CacheManager.clearCache();
249            Runtime.getRuntime().gc();
250            lastTimeRanGC = System.currentTimeMillis();
251        }
252    
253        /**
254         * Show the statistics.
255         */
256        private void showStats() throws IllegalStateException {
257            label.setToolTipText(getToolTip());
258            if (showClock) {
259                Date d = new Date();
260                clockFormat.setTimeZone(GuiUtils.getTimeZone());
261                label.setText("  " + clockFormat.format(d));
262                repaint();
263                return;
264            }
265    
266            double totalMemory = Runtime.getRuntime().maxMemory();
267            double highWaterMark = Runtime.getRuntime().totalMemory();
268            double freeMemory = Runtime.getRuntime().freeMemory();
269            double usedMemory = (highWaterMark - freeMemory);
270    
271            double megabyte = 1024 * 1024;
272    
273            totalMemory = totalMemory / megabyte;
274            usedMemory = usedMemory / megabyte;
275            highWaterMark = highWaterMark / megabyte;
276    
277            long now = System.currentTimeMillis();
278            if (lastTimeRanGC < 0)
279                lastTimeRanGC = now;
280    
281            // For the threshold use the physical memory
282            int percent = (int)(100.0f * (usedMemory / totalMemory));
283            if (percent > percentThreshold) {
284                timesAboveThreshold++;
285                if (timesAboveThreshold > 5) {
286                    // Only run every 5 seconds
287                    if (now - lastTimeRanGC > 5000) {
288                        // For now just clear the cache. Don't run the gc
289                        CacheManager.clearCache();
290                        // runGC();
291                        lastTimeRanGC = now;
292                    }
293                }
294                int stretchedPercent = Math.round(((float)percent - (float)percentThreshold) * (100.0f / (100.0f - (float)percentThreshold)));
295                label.setBackground(doColorThing(stretchedPercent));
296            } else {
297                timesAboveThreshold = 0;
298                lastTimeRanGC = now;
299                label.setBackground(doColorThing(0));
300            }
301            
302            // TODO: evaluate this method--should we really cancel stuff for the user?
303            // Decided that no, we shouldn't.  At least not until we get a more bulletproof way of doing it.
304            // action:idv.stopload is unreliable and doesnt seem to stop object creation, just data loading.
305            if (percent > this.percentCancel) {
306                if (!triedToCancel) {
307    //              System.err.println("Canceled the load... not much memory available");
308    //              idv.handleAction("action:idv.stopload");
309                    triedToCancel = true;
310                }
311            }
312            else {
313                triedToCancel = false;
314            }
315    
316            label.setText(" "
317                + Msg.msg("Memory:") + " "
318                + fmt.format(usedMemory) + "/"
319                + fmt.format(highWaterMark) + "/"
320                + fmt.format(totalMemory) + " " + Msg.msg("MB")
321                + " ");
322    
323            repaint();
324        }
325    
326        private Color doColorThing(final int percent) {
327            Float alpha = new Float(percent).floatValue() / 100;
328            return new Color(1.0f, 0.0f, 0.0f, alpha);
329        }
330    
331        /**
332         * Run this monitor
333         */
334        public void run() {
335            while (running) {
336                try {
337                    showStats();
338                    Thread.sleep(sleepInterval);
339                } catch (Exception exc) {
340                }
341            }
342        }
343    
344        /**
345         * Set whether we are running
346         * 
347         * @param r true if we are running
348         */
349        public void setRunning(final boolean r) {
350            running = r;
351        }
352    
353        /**
354         * Get whether we are running
355         * 
356         * @return true if we are
357         */
358        public boolean getRunning() {
359            return running;
360        }
361    
362        /**
363         * Test routine
364         * 
365         * @param args not used
366         */
367        public static void main(final String[] args) {
368            JFrame f = new JFrame();
369            MemoryMonitor mm = new MemoryMonitor(null);
370            f.getContentPane().add(mm);
371            f.pack();
372            f.setVisible(true);
373        }
374    
375    }