001 /* 002 // $Id: //open/mondrian/src/main/mondrian/util/AbstractMemoryMonitor.java#6 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 2007-2008 Julian Hyde and others 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 // 010 */ 011 package mondrian.util; 012 013 import mondrian.olap.MondrianProperties; 014 import org.apache.log4j.Logger; 015 import java.util.LinkedList; 016 import java.util.ListIterator; 017 018 /** 019 * Abstract implementation of {@link MemoryMonitor}. Base class 020 * for different memory monitoring strategies. 021 * 022 * @author Richard M. Emberson 023 * @since Feb 03 2007 024 * @version $Id: //open/mondrian/src/main/mondrian/util/AbstractMemoryMonitor.java#6 $ 025 */ 026 public abstract class AbstractMemoryMonitor 027 implements MemoryMonitor, MemoryMonitor.Test { 028 029 /** 030 * Basically, 100 percent. 031 */ 032 private static final int MAX_PERCENTAGE = 100; 033 034 /** 035 * Class used to associate <code>Listener</code> and threshold. 036 */ 037 static class Entry { 038 final Listener listener; 039 long threshold; 040 041 /** 042 * Creates an Entry. 043 * 044 * @param listener Listener 045 * @param threshold Threshold percentage which will cause notification 046 */ 047 Entry(final Listener listener, final long threshold) { 048 this.listener = listener; 049 this.threshold = threshold; 050 } 051 public boolean equals(final Object other) { 052 return (other instanceof Entry) && 053 (listener == ((Entry) other).listener); 054 } 055 public int hashCode() { 056 return listener.hashCode(); 057 } 058 } 059 060 /** 061 * <code>LinkedList</code> of <code>Entry</code> objects. A 062 * <code>LinkedList</code> was used for quick insertion and 063 * removal. 064 */ 065 private final LinkedList<Entry> listeners; 066 067 /** 068 * The current low threshold level. This is the lowest level of any 069 * of the registered <code>Listener</code>s. 070 */ 071 private long lowThreshold; 072 073 /** 074 * Constructor of this base class. 075 */ 076 protected AbstractMemoryMonitor() { 077 listeners = new LinkedList<Entry>(); 078 } 079 080 /** 081 * Returns the <code>Logger</code>. 082 * 083 * @return the <code>Logger</code>. 084 */ 085 protected abstract Logger getLogger(); 086 087 /** 088 * Returns the current lowest threshold of all registered 089 * <code>Listener</code>s. 090 * 091 * @return the lowest threshold. 092 */ 093 protected long getLowThreshold() { 094 return lowThreshold; 095 } 096 097 /** 098 * Returns the default memory notification percentage. 099 * 100 * <p>This is the value of the Mondrian 101 * {@link MondrianProperties#MemoryMonitorThreshold} property. 102 * 103 * @return the default threshold percentage. 104 */ 105 public int getDefaultThresholdPercentage() { 106 return MondrianProperties.instance().MemoryMonitorThreshold.get(); 107 } 108 109 public boolean addListener(final Listener listener) { 110 return addListener(listener, getDefaultThresholdPercentage()); 111 } 112 113 public boolean addListener(Listener listener, int percentage) { 114 getLogger().info("addListener enter"); 115 try { 116 /* 117 // Should this listener being added be immediately 118 // notified that memory is short. 119 boolean notifyNow = (usagePercentage() >= percentage); 120 */ 121 122 final long newThreshold = convertPercentageToThreshold(percentage); 123 Entry e = new Entry(listener, newThreshold); 124 125 synchronized (listeners) { 126 long prevLowThreshold = generateLowThreshold(); 127 128 // Add the new listener to its proper place in the 129 // list of listeners based upon threshold value. 130 final ListIterator<Entry> iter = listeners.listIterator(); 131 while (iter.hasNext()) { 132 Entry ee = iter.next(); 133 if (newThreshold <= ee.threshold) { 134 iter.add(e); 135 e = null; 136 break; 137 } 138 } 139 // If not null, then it has not been added yet, 140 // either its the first one or its the biggest. 141 if (e != null) { 142 listeners.addLast(e); 143 } 144 145 // If the new threshold is less than the previous 146 // lowest threshold, then notify the Java5 system 147 // that we are interested in being notified for this 148 // lower value. 149 lowThreshold = generateLowThreshold(); 150 if (lowThreshold < prevLowThreshold) { 151 notifyNewLowThreshold(lowThreshold); 152 } 153 } 154 /* 155 if (notifyNow) { 156 listener.memoryUsageNotification(getUsedMemory(), getMaxMemory()); 157 } 158 */ 159 return true; 160 161 } finally { 162 getLogger().info("addListener exit"); 163 } 164 } 165 166 public void updateListenerThreshold(Listener listener, int percentage) { 167 getLogger().info("updateListenerThreshold enter"); 168 try { 169 /* 170 // Should this listener being added be immediately 171 // notified that memory is short. 172 boolean notifyNow = (usagePercentage() >= percentage); 173 */ 174 175 final long newThreshold = convertPercentageToThreshold(percentage); 176 177 synchronized (listeners) { 178 long prevLowThreshold = generateLowThreshold(); 179 180 Entry e = null; 181 // Remove the listener from the list of listeners. 182 ListIterator<Entry> iter = listeners.listIterator(); 183 while (iter.hasNext()) { 184 e = iter.next(); 185 if (e.listener == listener) { 186 iter.remove(); 187 break; 188 } else { 189 e = null; 190 } 191 } 192 // If 'e' is not null, then the listener was found. 193 if (e != null) { 194 e.threshold = newThreshold; 195 196 // Add the listener. 197 iter = listeners.listIterator(); 198 while (iter.hasNext()) { 199 Entry ee = iter.next(); 200 if (newThreshold <= ee.threshold) { 201 iter.add(e); 202 break; 203 } 204 } 205 lowThreshold = generateLowThreshold(); 206 if (lowThreshold != prevLowThreshold) { 207 notifyNewLowThreshold(lowThreshold); 208 } 209 } 210 } 211 212 /* 213 if (notifyNow) { 214 listener.memoryUsageNotification(getUsedMemory(), getMaxMemory()); 215 } 216 */ 217 218 } finally { 219 getLogger().info("updateListenerThreshold exit"); 220 } 221 } 222 223 public boolean removeListener(Listener listener) { 224 getLogger().info("removeListener enter"); 225 try { 226 boolean result = false; 227 synchronized (listeners) { 228 long prevLowThreshold = generateLowThreshold(); 229 230 final ListIterator<Entry> iter = listeners.listIterator(); 231 while (iter.hasNext()) { 232 Entry ee = iter.next(); 233 if (listener == ee.listener) { 234 iter.remove(); 235 result = true; 236 break; 237 } 238 } 239 240 // If there is a new low threshold, tell Java5 241 lowThreshold = generateLowThreshold(); 242 if (lowThreshold > prevLowThreshold) { 243 notifyNewLowThreshold(lowThreshold); 244 } 245 246 } 247 return result; 248 } finally { 249 getLogger().info("removeListener exit"); 250 } 251 } 252 253 public void removeAllListener() { 254 getLogger().info("removeAllListener enter"); 255 try { 256 listeners.clear(); 257 notifyNewLowThreshold(generateLowThreshold()); 258 } finally { 259 getLogger().info("removeAllListener exit"); 260 } 261 } 262 263 /** 264 * Returns the lowest threshold from the list of <code>Listener</code>s. 265 * If there are no <code>Listener</code>s, then return the maximum 266 * memory usage. Returns <code>Long.MAX_VALUE</code> if there 267 * are no <code>Listener</code>s 268 * 269 * @return the lowest threshold or <code>Long.MAX_VALUE</code> 270 */ 271 protected long generateLowThreshold() { 272 // The Long.MAX_VALUE is used to communicate to the 273 // notifyNewLowThreshold method that it should set the value to zero. 274 return listeners.isEmpty() 275 ? Long.MAX_VALUE 276 : listeners.get(0).threshold; 277 } 278 279 280 /** 281 * Notifies all <code>Listener</code>s that memory is running short. 282 * 283 * @param usedMemory the current memory used. 284 * @param maxMemory the maximum memory. 285 */ 286 protected void notifyListeners(final long usedMemory, 287 final long maxMemory) { 288 synchronized (listeners) { 289 for (Entry e : listeners) { 290 if (usedMemory >= e.threshold) { 291 e.listener.memoryUsageNotification(usedMemory, 292 maxMemory); 293 } 294 } 295 } 296 } 297 298 /** 299 * Derived classes implement this method if they wish to be notified 300 * when there is a new lowest threshold. 301 * 302 * @param newLowThreshold the new low threshold. 303 */ 304 protected void notifyNewLowThreshold(final long newLowThreshold) { 305 // empty 306 } 307 308 /** 309 * Converts a percentage threshold to its corresponding memory value, 310 * (percentage * maximum-memory / 100). 311 * 312 * @param percentage the threshold. 313 * @return the memory value. 314 */ 315 protected long convertPercentageToThreshold(final int percentage) { 316 if (percentage < 0 || percentage > MAX_PERCENTAGE) { 317 throw new IllegalArgumentException( 318 "Percentage not in range: " + percentage); 319 } 320 321 long maxMemory = getMaxMemory(); 322 return (maxMemory * percentage) / MAX_PERCENTAGE; 323 } 324 325 /** 326 * Converts a memory value to its percentage. 327 * 328 * @param threshold the memory value. 329 * @return the percentage. 330 */ 331 protected int convertThresholdToPercentage(final long threshold) { 332 long maxMemory = getMaxMemory(); 333 return (int) ((MAX_PERCENTAGE * threshold) / maxMemory); 334 } 335 336 /** 337 * Returns how much memory is currently being used as a percentage. 338 * 339 * @return currently used memory as a percentage. 340 */ 341 protected int usagePercentage() { 342 return convertThresholdToPercentage(getUsedMemory()); 343 } 344 345 public void resetFromTest() { 346 long lowThreshold = generateLowThreshold(); 347 notifyNewLowThreshold(lowThreshold); 348 } 349 } 350 351 // End AbstractMemoryMonitor.java