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