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 }