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