/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cloudfoundry.java.test.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Utility methods used during memory tests
*/
@Component
public class MemoryUtils {
private static final double BYTE = 1;
private static final double KIBI = 1024 * BYTE;
private static final double MIBI = 1024 * KIBI;
private static final double GIBI = 1048 * MIBI;
private final ExecutorService executor = Executors.newFixedThreadPool(2);
private final Map<String, String> environment;
private final long outOfMemorySize;
/**
* Creates an instance of the utility
*/
public MemoryUtils() {
this(System.getenv(), ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax());
}
MemoryUtils(Map<String, String> environment, long outOfMemorySize) {
this.environment = environment;
this.outOfMemorySize = outOfMemorySize;
}
@PostConstruct
public void onStart() {
outOfMemoryOnStart();
}
/**
* Generates an {@link OutOfMemoryError} if the {@code FAIL_OOM} environment variable is {@code true}. Otherwise
* does nothing.
*
* @return Never returns as it will generate an {@link OutOfMemoryError}
*/
public byte[][] outOfMemoryOnStart() {
String value = this.environment.get("FAIL_OOM");
if ((value != null) && Boolean.parseBoolean(value)) {
return outOfMemory();
}
return null;
}
public byte[][] outOfMemory() {
try {
AtomicBoolean flag = new AtomicBoolean();
this.executor.submit(new MemorySizeLogger(flag));
return this.executor.submit(new MemoryExhauster(flag, this.outOfMemorySize)).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
private String ibi(long size) {
if (size > GIBI) {
return String.format("%.1f GiB", (size / GIBI));
} else if (size > MIBI) {
return String.format("%.1f MiB", (size / MIBI));
} else if (size > KIBI) {
return String.format("%.1f KiB", (size / KIBI));
} else {
return String.format("%d B", size);
}
}
private final class MemorySizeLogger implements Runnable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
private final AtomicBoolean flag;
public MemorySizeLogger(AtomicBoolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
while (!this.flag.get()) {
this.logger.info(message());
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.interrupted();
}
}
private String message() {
MemoryUsage heapMemoryUsage = this.memoryMXBean.getHeapMemoryUsage();
long used = heapMemoryUsage.getUsed();
long max = heapMemoryUsage.getMax();
return String.format("%s of %s used", ibi(used), ibi(max));
}
}
private class MemoryExhauster implements Callable<byte[][]> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final AtomicBoolean flag;
private final long outOfMemorySize;
public MemoryExhauster(AtomicBoolean flag, long outOfMemorySize) {
this.flag = flag;
this.outOfMemorySize = outOfMemorySize;
}
@Override
public byte[][] call() throws Exception {
this.logger.info("Exhausting {} of memory", ibi(this.outOfMemorySize));
int size = (int) Math.sqrt(this.outOfMemorySize);
byte[][] bytes = new byte[size][];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = new byte[size];
}
this.flag.set(true);
return bytes;
}
}
}