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