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