blob: 40cf083a0dc6b74e4ea6874570b873b401e65caf [file] [log] [blame]
package com.gemstone.gemfire.internal.cache.control;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import com.gemstone.gemfire.DataSerializer;
import com.gemstone.gemfire.cache.LowMemoryException;
import com.gemstone.gemfire.cache.control.ResourceManager;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
/**
* Stores eviction and critical thresholds for memory as well as the logic for
* determining how memory transitions between states.
*
* @author David Hoots
* @since 9.0
*/
public class MemoryThresholds {
public enum MemoryState {
DISABLED, // Both eviction and critical disabled
EVICTION_DISABLED, // Eviction disabled, critical enabled, critical threshold not exceeded
EVICTION_DISABLED_CRITICAL, // Eviction disabled, critical enabled, critical threshold exceeded
CRITICAL_DISABLED, // Critical disabled, eviction enabled, eviction threshold not exceeded
EVICTION_CRITICAL_DISABLED, // Critical disabled, eviction enabled, eviction threshold exceeded
NORMAL, // Both eviction and critical enabled, neither threshold exceeded
EVICTION, // Both eviction and critical enabled, eviction threshold exceeded
CRITICAL, // Both eviction and critical enabled, critical threshold exceeded
EVICTION_CRITICAL; // Both eviction and critical enabled, both thresholds exceeded
public static MemoryState fromData(DataInput in) throws IOException {
return MemoryState.values()[in.readInt()];
}
public void toData(DataOutput out) throws IOException {
DataSerializer.writeInteger(this.ordinal(), out);
}
public boolean isEvictionDisabled() {
return (this == DISABLED || this == EVICTION_DISABLED_CRITICAL || this == EVICTION_DISABLED);
}
public boolean isCriticalDisabled() {
return (this == DISABLED || this == EVICTION_CRITICAL_DISABLED || this == CRITICAL_DISABLED);
}
public boolean isNormal() {
return (this == NORMAL || this == EVICTION_DISABLED || this == CRITICAL_DISABLED);
}
public boolean isEviction() {
return (this == EVICTION || this == EVICTION_CRITICAL_DISABLED || this == EVICTION_CRITICAL);
}
public boolean isCritical() {
return (this == CRITICAL || this == EVICTION_DISABLED_CRITICAL || this == EVICTION_CRITICAL);
}
}
/**
* When this property is set to true, a {@link LowMemoryException} is not
* thrown, even when usage crosses the critical threshold.
*/
private static final boolean DISABLE_LOW_MEM_EXCEPTION = Boolean.getBoolean("gemfire.disableLowMemoryException");
/**
* The default percent of memory at which the VM is considered in a
* critical state.
*/
public static final float DEFAULT_CRITICAL_PERCENTAGE = ResourceManager.DEFAULT_CRITICAL_PERCENTAGE;
/**
* The default percent of memory at which the VM should begin evicting
* data. Note that if a LRU is created and the eviction percentage
* has not been set then it will default to <code>80.0</code> unless the
* critical percentage has been set in which case it will default to a
* value <code>5.0</code> less than the critical percentage.
*/
public static final float DEFAULT_EVICTION_PERCENTAGE = ResourceManager.DEFAULT_EVICTION_PERCENTAGE;
/**
* Memory usage must fall below THRESHOLD-THRESHOLD_THICKNESS before we deliver
* a down event
*/
private static final double THRESHOLD_THICKNESS = Double.parseDouble(System.getProperty("gemfire.thresholdThickness",
"2.00"));
/**
* Memory usage must fall below THRESHOLD-THRESHOLD_THICKNESS_EVICT before we
* deliver an eviction down event
*/
private static final double THRESHOLD_THICKNESS_EVICT = Double.parseDouble(System.getProperty(
"gemfire.eviction-thresholdThickness", Double.toString(THRESHOLD_THICKNESS)));
private final long maxMemoryBytes;
// Percent of available memory at which point a critical state is entered
private final float criticalThreshold;
// Number of bytes used at which point memory will enter the critical state
private final long criticalThresholdBytes;
// Percent of available memory at which point an eviction state is entered
private final float evictionThreshold;
// Number of bytes used at which point memory will enter the eviction state
private final long evictionThresholdBytes;
// Number of bytes used below which memory will leave the critical state
private final long criticalThresholdClearBytes;
// Number of bytes used below which memory will leave the eviction state
private final long evictionThresholdClearBytes;
MemoryThresholds(long maxMemoryBytes) {
this(maxMemoryBytes, DEFAULT_CRITICAL_PERCENTAGE, DEFAULT_EVICTION_PERCENTAGE);
}
/**
* Public for testing.
*/
public MemoryThresholds(long maxMemoryBytes, float criticalThreshold, float evictionThreshold) {
if (criticalThreshold > 100.0f || criticalThreshold < 0.0f) {
throw new IllegalArgumentException(LocalizedStrings.MemoryThresholds_CRITICAL_PERCENTAGE_GT_ZERO_AND_LTE_100
.toLocalizedString());
}
if (evictionThreshold > 100.0f || evictionThreshold < 0.0f) {
throw new IllegalArgumentException(LocalizedStrings.MemoryThresholds_EVICTION_PERCENTAGE_GT_ZERO_AND_LTE_100
.toLocalizedString());
}
if (evictionThreshold != 0 && criticalThreshold != 0 && evictionThreshold >= criticalThreshold) {
throw new IllegalArgumentException(LocalizedStrings.MemoryThresholds_CRITICAL_PERCENTAGE_GTE_EVICTION_PERCENTAGE
.toLocalizedString());
}
this.maxMemoryBytes = maxMemoryBytes;
this.criticalThreshold = criticalThreshold;
this.criticalThresholdBytes = (long) (criticalThreshold * 0.01 * maxMemoryBytes);
this.criticalThresholdClearBytes = (long) (this.criticalThresholdBytes - (0.01 * THRESHOLD_THICKNESS * this.maxMemoryBytes));
this.evictionThreshold = evictionThreshold;
this.evictionThresholdBytes = (long) (evictionThreshold * 0.01 * maxMemoryBytes);
this.evictionThresholdClearBytes = (long) (this.evictionThresholdBytes - (0.01 * THRESHOLD_THICKNESS_EVICT * this.maxMemoryBytes));
}
public static final boolean isLowMemoryExceptionDisabled() {
return DISABLE_LOW_MEM_EXCEPTION;
}
public MemoryState computeNextState(final MemoryState oldState, final long bytesUsed) {
assert oldState != null;
assert bytesUsed >= 0;
// Are both eviction and critical thresholds enabled?
if (this.evictionThreshold != 0 && this.criticalThreshold != 0) {
if (bytesUsed < this.evictionThresholdClearBytes || (!oldState.isEviction() && bytesUsed < this.evictionThresholdBytes)) {
return MemoryState.NORMAL;
}
if (bytesUsed < this.criticalThresholdClearBytes || (!oldState.isCritical() && bytesUsed < this.criticalThresholdBytes)) {
return MemoryState.EVICTION;
}
return MemoryState.EVICTION_CRITICAL;
}
// Are both eviction and critical thresholds disabled?
if (this.evictionThreshold == 0 && this.criticalThreshold == 0) {
return MemoryState.DISABLED;
}
// Is just critical threshold enabled?
if (this.evictionThreshold == 0) {
if (bytesUsed < this.criticalThresholdClearBytes || (!oldState.isCritical() && bytesUsed < this.criticalThresholdBytes)) {
return MemoryState.EVICTION_DISABLED;
}
return MemoryState.EVICTION_DISABLED_CRITICAL;
}
// Just the eviction threshold is enabled
if (bytesUsed < this.evictionThresholdClearBytes || (!oldState.isEviction() && bytesUsed < this.evictionThresholdBytes)) {
return MemoryState.CRITICAL_DISABLED;
}
return MemoryState.EVICTION_CRITICAL_DISABLED;
}
@Override
public String toString() {
return new StringBuilder().append("MemoryThresholds@[").append(System.identityHashCode(this))
.append(" maxMemoryBytes:" + this.maxMemoryBytes)
.append(", criticalThreshold:" + this.criticalThreshold)
.append(", criticalThresholdBytes:" + this.criticalThresholdBytes)
.append(", criticalThresholdClearBytes:" + this.criticalThresholdClearBytes)
.append(", evictionThreshold:" + this.evictionThreshold)
.append(", evictionThresholdBytes:" + this.evictionThresholdBytes)
.append(", evictionThresholdClearBytes:" + this.evictionThresholdClearBytes)
.append("]").toString();
}
public long getMaxMemoryBytes() {
return this.maxMemoryBytes;
}
public float getCriticalThreshold() {
return this.criticalThreshold;
}
public long getCriticalThresholdBytes() {
return this.criticalThresholdBytes;
}
public long getCriticalThresholdClearBytes() {
return this.criticalThresholdClearBytes;
}
public boolean isCriticalThresholdEnabled() {
return this.criticalThreshold > 0.0f;
}
public float getEvictionThreshold() {
return this.evictionThreshold;
}
public long getEvictionThresholdBytes() {
return this.evictionThresholdBytes;
}
public long getEvictionThresholdClearBytes() {
return this.evictionThresholdClearBytes;
}
public boolean isEvictionThresholdEnabled() {
return this.evictionThreshold > 0.0f;
}
/**
* Generate a Thresholds object from data available from the DataInput
*
* @param in
* DataInput from which to read the data
* @return a new instance of Thresholds
* @throws IOException
*/
public static MemoryThresholds fromData(DataInput in) throws IOException {
long maxMemoryBytes = in.readLong();
float criticalThreshold = in.readFloat();
float evictionThreshold = in.readFloat();
return new MemoryThresholds(maxMemoryBytes, criticalThreshold, evictionThreshold);
}
/**
* Write the state of this to the DataOutput
*
* @param out
* DataOutput on which to write internal state
* @throws IOException
*/
public void toData(DataOutput out) throws IOException {
out.writeLong(this.maxMemoryBytes);
out.writeFloat(this.criticalThreshold);
out.writeFloat(this.evictionThreshold);
}
}