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