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 }