com.networknt.chaos.MemoryAssaultHandler Maven / Gradle / Ivy
The newest version!
package com.networknt.chaos;
import com.networknt.config.Config;
import com.networknt.handler.Handler;
import com.networknt.handler.MiddlewareHandler;
import com.networknt.utility.ModuleRegistry;
import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Vector;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class MemoryAssaultHandler implements MiddlewareHandler {
public static MemoryAssaultConfig config = (MemoryAssaultConfig) Config.getInstance().getJsonObjectConfig(MemoryAssaultConfig.CONFIG_NAME, MemoryAssaultConfig.class);
private static final Logger logger = LoggerFactory.getLogger(MemoryAssaultHandler.class);
private static final AtomicLong stolenMemory = new AtomicLong(0);
private volatile HttpHandler next;
private Runtime runtime = Runtime.getRuntime();
public MemoryAssaultHandler() {
logger.info("MemoryAssaultHandler constructed");
}
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
if(logger.isDebugEnabled()) logger.debug("MemoryAssaultHandler.handleRequest starts.");
if(isEnabled() && isTrouble() && !config.isBypass()) {
logger.info("Chaos Monkey - I am eating free memory!");
eatFreeMemory();
}
if(logger.isDebugEnabled()) logger.debug("MemoryAssaultHandler.handleRequest ends.");
Handler.next(exchange, next);
}
@Override
public HttpHandler getNext() {
return next;
}
@Override
public MiddlewareHandler setNext(final HttpHandler next) {
Handlers.handlerNotNull(next);
this.next = next;
return this;
}
@Override
public boolean isEnabled() {
return config.isEnabled();
}
@Override
public void register() {
ModuleRegistry.registerModule(MemoryAssaultConfig.CONFIG_NAME, MemoryAssaultHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(MemoryAssaultConfig.CONFIG_NAME), null);
}
@Override
public void reload() {
config = (MemoryAssaultConfig) Config.getInstance().getJsonObjectConfig(MemoryAssaultConfig.CONFIG_NAME, MemoryAssaultConfig.class);
}
private void eatFreeMemory() {
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
Vector memoryVector = new Vector<>();
long stolenMemoryTotal = 0L;
while (isEnabled()) {
// overview of memory methods in java https://stackoverflow.com/a/18375641
long freeMemory = runtime.freeMemory();
long usedMemory = runtime.totalMemory() - freeMemory;
if (cannotAllocateMoreMemory()) {
logger.debug("Cannot allocate more memory");
break;
}
logger.debug("Used memory in bytes: " + usedMemory);
stolenMemoryTotal = stealMemory(memoryVector, stolenMemoryTotal, getBytesToSteal());
waitUntil(config.getMemoryMillisecondsWaitNextIncrease());
}
// Hold memory level and cleanUp after, only if experiment is running
if (isEnabled()) {
logger.info("Memory fill reached, now sleeping and holding memory");
waitUntil(config.getMemoryMillisecondsHoldFilledMemory());
}
// clean Vector
memoryVector.clear();
// quickly run gc for reuse
runtime.gc();
long stolenAfterComplete = stolenMemory.addAndGet(-stolenMemoryTotal);
}
private boolean cannotAllocateMoreMemory() {
double limit =
runtime.maxMemory() * config.getMemoryFillTargetFraction();
return runtime.totalMemory() > Math.floor(limit);
}
private int getBytesToSteal() {
int amount = (int)(runtime.freeMemory()
* config.getMemoryFillIncrementFraction());
boolean isJava8 = System.getProperty("java.version").startsWith("1.8");
// seems filling more than 256 MB per slice is bad on java 8
// we keep running into heap errors and other OOMs.
return isJava8 ? Math.min(MemorySizeConverter.toBytes(256), amount) : amount;
}
private long stealMemory(Vector memoryVector, long stolenMemoryTotal, int bytesToSteal) {
memoryVector.add(createDirtyMemorySlice(bytesToSteal));
stolenMemoryTotal += bytesToSteal;
long newStolenTotal = stolenMemory.addAndGet(bytesToSteal);
logger.debug(
"Chaos Monkey - memory assault increase, free memory: "
+ MemorySizeConverter.toMegabytes(runtime.freeMemory()));
return stolenMemoryTotal;
}
private byte[] createDirtyMemorySlice(int size) {
byte[] b = new byte[size];
for (int idx = 0; idx < size; idx += 4096) { // 4096
// is commonly the size of a memory page, forcing a commit
b[idx] = 19;
}
return b;
}
private void waitUntil(int ms) {
final long startNano = System.nanoTime();
long now = startNano;
while (startNano + TimeUnit.MILLISECONDS.toNanos(ms) > now && isEnabled()) {
try {
long elapsed = TimeUnit.NANOSECONDS.toMillis(startNano - now);
Thread.sleep(Math.min(100, ms - elapsed));
now = System.nanoTime();
} catch (InterruptedException e) {
break;
}
}
}
private boolean isTrouble() {
return getTroubleRandom() >= config.getLevel();
}
public int getTroubleRandom() {
return ThreadLocalRandom.current().nextInt(1, config.getLevel() + 1);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy