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