Skip to content

Commit

Permalink
MONDRIAN
Browse files Browse the repository at this point in the history
   Could not create test case that could always quickly demonstrate
   Java5 memory monitoring (across 2 orders of magnitude of heap size,
   running the test case by itself always worked, just not with other tests).
   Added ability for an ObjectFactory.Singleton to be tested.

[git-p4: depot-paths = "//open/mondrian/": change = 8686]
  • Loading branch information
Richard Emberson committed Feb 8, 2007
1 parent e1f02af commit 3a1d816
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 87 deletions.
57 changes: 36 additions & 21 deletions src/main/mondrian/util/AbstractMemoryMonitor.java
Expand Up @@ -16,18 +16,20 @@
import java.util.ListIterator;

/**
* Abstract implementation of <code>MemoryMonitor</code>.
* Abstract implementation of <code>MemoryMonitor</code>. Base class
* for different memory monitoring strategies.
*
* @author <a>Richard M. Emberson</a>
* @since Feb 03 2007
* @version $Id$
*/
public abstract class AbstractMemoryMonitor implements MemoryMonitor {
public abstract class AbstractMemoryMonitor
implements MemoryMonitor, MemoryMonitor.Test {

/**
* Basically, 100 percent.
*/
private static final int MAX_THRESHOLD = 100;
private static final int MAX_PERCENTAGE = 100;

/**
* Class used to associate <code>Listener</code> and threshold.
Expand Down Expand Up @@ -128,9 +130,11 @@ public boolean addListener(final Listener listener) {
public boolean addListener(Listener listener, int percentage) {
getLogger().info("addListener enter");
try {
/*
// Should this listener being added be immediately
// notified that memory is short.
boolean notifyNow = (usagePercentage() >= percentage);
*/

final long newThreshold = convertPercentageToThreshold(percentage);
Entry e = new Entry(listener, newThreshold);
Expand Down Expand Up @@ -164,9 +168,11 @@ public boolean addListener(Listener listener, int percentage) {
notifyNewLowThreshold(lowThreshold);
}
}
/*
if (notifyNow) {
listener.memoryUsageNotification(getUsed(), getMax());
listener.memoryUsageNotification(getUsedMemory(), getMaxMemory());
}
*/
return true;

} finally {
Expand All @@ -187,9 +193,11 @@ public boolean addListener(Listener listener, int percentage) {
public void updateListenerThreshold(Listener listener, int percentage) {
getLogger().info("updateListenerThreshold enter");
try {
/*
// Should this listener being added be immediately
// notified that memory is short.
boolean notifyNow = (usagePercentage() >= percentage);
*/

final long newThreshold = convertPercentageToThreshold(percentage);

Expand Down Expand Up @@ -228,9 +236,11 @@ public void updateListenerThreshold(Listener listener, int percentage) {
}
}

/*
if (notifyNow) {
listener.memoryUsageNotification(getUsed(), getMax());
listener.memoryUsageNotification(getUsedMemory(), getMaxMemory());
}
*/

} finally {
getLogger().info("updateListenerThreshold exit");
Expand Down Expand Up @@ -275,17 +285,14 @@ public boolean removeListener(Listener listener) {
}

/**
* This is not part of the <code>MemoryMonitor</code> interface.
* Clients that "know" that they have an instance of the
* <code>AbstractMemoryMonitor</code> can cast and call this if required.
* It is a back door to clear out out Listeners and turnoff
* Removes a <code>Listener</code>s
* JVN memory notification.
*/
public void removeAllListener() {
getLogger().info("removeAllListener enter");
try {
notifyNewLowThreshold(getMax());
listeners.clear();
notifyNewLowThreshold(generateLowThreshold());
} finally {
getLogger().info("removeAllListener exit");
}
Expand All @@ -294,13 +301,16 @@ public void removeAllListener() {
/**
* Returns the lowest threshold from the list of <code>Listener</code>s.
* If there are no <code>Listener</code>s, then return the maximum
* memory usage.
* memory usage. Returns <code>Long.MAX_VALUE</code> if there
* are no <code>Listener</code>s
*
* @return the lowest threshold or maximum memory usage.
* @return the lowest threshold or <code>Long.MAX_VALUE</code>
*/
protected long generateLowThreshold() {
// The Long.MAX_VALUE is used to communicate to the
// notifyNewLowThreshold method that it should set the value to zero.
return listeners.isEmpty()
? getMax()
? Long.MAX_VALUE
: listeners.get(0).threshold;
}

Expand Down Expand Up @@ -338,14 +348,14 @@ protected void notifyNewLowThreshold(final long newLowThreshold) {
*
* @return the maximum memory usage.
*/
protected abstract long getMax();
public abstract long getMaxMemory();

/**
* Returns the current memory used.
*
* @return the current memory used.
*/
protected abstract long getUsed();
public abstract long getUsedMemory();

/**
* Converts a percentage threshold to its corresponding memory value,
Expand All @@ -355,13 +365,13 @@ protected void notifyNewLowThreshold(final long newLowThreshold) {
* @return the memory value.
*/
protected long convertPercentageToThreshold(final int percentage) {
if (percentage < 0 || percentage > MAX_THRESHOLD) {
if (percentage < 0 || percentage > MAX_PERCENTAGE) {
throw new IllegalArgumentException(
"Percentage not in range: " +percentage);
}

long maxMemory = getMax();
long l = (maxMemory * percentage) / MAX_THRESHOLD;
long maxMemory = getMaxMemory();
long l = (maxMemory * percentage) / MAX_PERCENTAGE;
return l;
}

Expand All @@ -372,8 +382,8 @@ protected long convertPercentageToThreshold(final int percentage) {
* @return the percentage.
*/
protected int convertThresholdToPercentage(final long threshold) {
long maxMemory = getMax();
int i = (int)( (MAX_THRESHOLD * threshold) / maxMemory);
long maxMemory = getMaxMemory();
int i = (int) ((MAX_PERCENTAGE * threshold) / maxMemory);
return i;
}

Expand All @@ -383,7 +393,12 @@ protected int convertThresholdToPercentage(final long threshold) {
* @return currently used memory as a percentage.
*/
protected int usagePercentage() {
return convertThresholdToPercentage(getUsed());
return convertThresholdToPercentage(getUsedMemory());
}

public void resetFromTest() {
long lowThreshold = generateLowThreshold();
notifyNewLowThreshold(lowThreshold);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/main/mondrian/util/FauxMemoryMonitor.java
Expand Up @@ -36,6 +36,15 @@ public void updateListenerThreshold(Listener listener, int percentage) {
public boolean removeListener(Listener listener) {
return true;
}
public void removeAllListener() {
// empty
}
public long getMaxMemory() {
return Runtime.getRuntime().maxMemory();
}
public long getUsedMemory() {
return Runtime.getRuntime().freeMemory();
}
}

// End FauxMemoryMonitor.java
48 changes: 48 additions & 0 deletions src/main/mondrian/util/MemoryMonitor.java
Expand Up @@ -104,6 +104,27 @@ public interface MemoryMonitor {
*/
boolean removeListener(Listener listener);

/**
* Clear out all <code>Listener</code>s and turnoff JVM
* memory notification.
*/
void removeAllListener();

/**
* Returns the maximum memory usage.
*
* @return the maximum memory usage.
*/
long getMaxMemory();

/**
* Returns the current memory used.
*
* @return the current memory used.
*/
long getUsedMemory();


/**
* A <code>MemoryMonitor</code> client implements the <code>Listener</code>
* interface and registers with the <code>MemoryMonitor</code>.
Expand Down Expand Up @@ -145,6 +166,33 @@ interface Listener {
*/
void memoryUsageNotification(long usedMemory, long maxMemory);
}

/**
* This is an interface that a <code>MemoryMonitor</code> may optionally
* implement. These methods give the tester access to some of the
* internal, white-box data.
* <p>
* During testing Mondrian has a default
* <code>MemoryMonitor</code> which might be replaced with a test
* <code>MemoryMonitor</code>s using the <code>ThreadLocal</code>
* mechanism. After the test using the test
* <code>MemoryMonitor</code> finishes, a call to the
* <code>resetFromTest</code> method allows
* the default <code>MemoryMonitor</code> reset itself.
* This is hook that should only be called as part of testing.
*/
interface Test {

/**
* This should only be called when one is switching from a
* test <code>MemoryMonitor</code> back to the default system
* <code>MemoryMonitor</code>. In particular, look at
* the <code>MemoryMonitorFactory</code>'s
* <code>clearThreadLocalClassName()</code> method for its
* usage.
*/
void resetFromTest();
}
}

// End MemoryMonitor.java
7 changes: 7 additions & 0 deletions src/main/mondrian/util/MemoryMonitorFactory.java
Expand Up @@ -97,6 +97,13 @@ public static void setThreadLocalClassName(String className) {
*/
public static void clearThreadLocalClassName() {
ClassName.set(null);
if (factory.testSingleInstance != null) {
factory.testSingleInstance.removeAllListener();
factory.testSingleInstance = null;
}
if (factory.singleInstance instanceof MemoryMonitor.Test) {
((MemoryMonitor.Test) factory.singleInstance).resetFromTest();
}
}

/**
Expand Down
11 changes: 8 additions & 3 deletions src/main/mondrian/util/NotificationMemoryMonitor.java
Expand Up @@ -121,23 +121,28 @@ protected Logger getLogger() {
* @param newLowThreshold the new threshold.
*/
protected void notifyNewLowThreshold(final long newLowThreshold) {
TENURED_POOL.setUsageThreshold(newLowThreshold);

if (newLowThreshold == Long.MAX_VALUE) {
TENURED_POOL.setUsageThreshold(0);
} else {
TENURED_POOL.setUsageThreshold(newLowThreshold);
}
}

/**
* Get the maximum possible memory usage for this JVM instance.
*
* @return maximum memory that can be used.
*/
protected long getMax() {
public long getMaxMemory() {
return TENURED_POOL.getUsage().getMax();
}
/**
* Get the current memory usage for this JVM instance.
*
* @return current memory used.
*/
protected long getUsed() {
public long getUsedMemory() {
return TENURED_POOL.getUsage().getUsed();
}
}
Expand Down
37 changes: 33 additions & 4 deletions src/main/mondrian/util/ObjectFactory.java
Expand Up @@ -373,8 +373,7 @@ protected V getObject(final String className,

} catch (Exception exc) {
throw new CreationException("Error creating object of type \"" +
getClass().getName() + "\"" ,
exc);
this.interfaceClass.getName() + "\"" , exc);
}
}

Expand Down Expand Up @@ -453,7 +452,7 @@ protected abstract V getDefault(Class[] parameterTypes,
// error using Util.newError, just like elsewhere in mondrian.
protected CreationException defaultCreationException() {
return new CreationException("Error creating object of type \"" +
getClass().getName() + "\"");
this.interfaceClass.getName() + "\"");
}

/**
Expand All @@ -467,6 +466,13 @@ public abstract static class Singleton<T> extends ObjectFactory<T> {
*/
protected T singleInstance;

/**
* The test single instance of the object created by the factory.
* Creating this <code>testSingleInstance</code> does not change the
* current value of the <code>singleInstance</code> variable.
*/
protected T testSingleInstance;

/**
* Creates a new singleton factory object. The
* <code>interfaceClass</code> parameter
Expand Down Expand Up @@ -507,7 +513,12 @@ public T getObject(final Properties props,
// Unit test override, do not use application instance.
final String className = getClassName();
if (className != null) {
return getObject(className, parameterTypes, parameterValues);
if (this.testSingleInstance == null) {
this.testSingleInstance = getTestObject(className,
parameterTypes,
parameterValues);
}
return this.testSingleInstance;
}

// NOTE: Should we distinguish between any Properties Object
Expand All @@ -530,6 +541,24 @@ public T getObject(final Properties props,
}
return this.singleInstance;
}

/**
* Create an instance for test purposes.
*
* @param className the class name used to create Object instance
* @param parameterTypes the class parameters that define the signature
* of the constructor to use
* @param parameterValues the values to use to construct the current
* instance of the object
* @return the newly created object
* @throws CreationException if unable to create the object
*/
protected T getTestObject(final String className,
final Class[] parameterTypes,
final Object[] parameterValues)
throws CreationException {
return getObject(className, parameterTypes, parameterValues);
}
}
}

Expand Down

0 comments on commit 3a1d816

Please sign in to comment.