Add an expiration strategy if the system has less than 5% memory available, fixes #364

This commit is contained in:
Guillaume Nodet
2021-03-18 20:47:34 +01:00
parent d03c143f94
commit 9991dce385

View File

@@ -15,6 +15,7 @@
*/
package org.mvndaemon.mvnd.daemon;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
@@ -22,15 +23,22 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.management.Attribute;
import javax.management.ObjectName;
import org.mvndaemon.mvnd.common.DaemonCompatibilitySpec;
import org.mvndaemon.mvnd.common.DaemonCompatibilitySpec.Result;
import org.mvndaemon.mvnd.common.DaemonExpirationStatus;
import org.mvndaemon.mvnd.common.DaemonInfo;
import org.mvndaemon.mvnd.common.DaemonState;
import org.mvndaemon.mvnd.common.Environment;
import org.mvndaemon.mvnd.common.Os;
import org.mvndaemon.mvnd.common.TimeUtils;
import org.mvndaemon.mvnd.nativ.CLibrary;
import static org.mvndaemon.mvnd.common.DaemonExpirationStatus.DO_NOT_EXPIRE;
import static org.mvndaemon.mvnd.common.DaemonExpirationStatus.GRACEFUL_EXPIRE;
@@ -78,8 +86,13 @@ public class DaemonExpiration {
}
static DaemonExpirationStrategy lowMemory(double minFreeMemoryPercentage) {
// TODO
return daemon -> NOT_TRIGGERED;
if (Os.current() == Os.MAC) {
return new OsxMemoryExpirationStrategy(minFreeMemoryPercentage);
} else if (Os.current() == Os.LINUX) {
return new MemInfoMemoryExpirationStrategy(minFreeMemoryPercentage);
} else {
return new MBeanMemoryExpirationStrategy(minFreeMemoryPercentage);
}
}
static DaemonExpirationStrategy duplicateGracePeriod() {
@@ -228,4 +241,127 @@ public class DaemonExpiration {
}
}
private static abstract class MemoryExpirationStrategy implements DaemonExpirationStrategy {
static final long MIN_THRESHOLD_BYTES = 384 * 1024 * 1024;
static final long MAX_THRESHOLD_BYTES = 1024 * 1024 * 1024;
final double minFreeMemoryPercentage;
public MemoryExpirationStrategy(double minFreeMemoryPercentage) {
this.minFreeMemoryPercentage = minFreeMemoryPercentage;
}
@Override
public DaemonExpirationResult checkExpiration(Server daemon) {
try {
long[] mem = getTotalAndFreeMemory();
if (mem != null && mem.length == 2) {
long total = mem[0];
long free = mem[1];
if (total > free && free > 0) {
double norm = Math.min(Math.max(total * minFreeMemoryPercentage, MIN_THRESHOLD_BYTES),
MAX_THRESHOLD_BYTES);
if (free < norm) {
return new DaemonExpirationResult(GRACEFUL_EXPIRE, "to reclaim system memory");
}
}
}
} catch (Exception e) {
// ignore
}
return NOT_TRIGGERED;
}
protected abstract long[] getTotalAndFreeMemory() throws Exception;
}
private static class OsxMemoryExpirationStrategy extends MemoryExpirationStrategy {
public OsxMemoryExpirationStrategy(double minFreeMemoryPercentage) {
super(minFreeMemoryPercentage);
}
@Override
protected long[] getTotalAndFreeMemory() throws Exception {
long[] mem = new long[2];
CLibrary.getOsxMemoryInfo(mem);
return mem;
}
}
private static class MemInfoMemoryExpirationStrategy extends MemoryExpirationStrategy {
public MemInfoMemoryExpirationStrategy(double minFreeMemoryPercentage) {
super(minFreeMemoryPercentage);
}
@Override
protected long[] getTotalAndFreeMemory() throws Exception {
Matcher m = Pattern.compile("^(\\S+):\\s+(\\d+) kB$").matcher("");
long total = -1;
long available = -1;
long free = -1;
long buffers = -1;
long cached = -1;
long reclaimable = -1;
long mapped = -1;
for (String line : Files.readAllLines(Paths.get("/proc/meminfo"))) {
if (m.reset(line).matches()) {
String key = m.group(1);
long val = Long.parseLong(m.group(2)) * 1024;
switch (key) {
case "MemTotal":
total = val;
break;
case "MemAvailable":
available = val;
break;
case "MemFree":
free = val;
break;
case "Buffers":
buffers = val;
break;
case "Cached":
cached = val;
break;
case "SReclaimable":
reclaimable = val;
break;
case "Mapped":
mapped = val;
break;
}
}
}
if (available < 0) {
if (free != -1 && buffers != -1 && cached != -1 && reclaimable != -1 && mapped != -1) {
available = free + buffers + cached + reclaimable - mapped;
}
}
return new long[] { total, available };
}
}
private static class MBeanMemoryExpirationStrategy extends MemoryExpirationStrategy {
final boolean isIbmJvm;
public MBeanMemoryExpirationStrategy(double minFreeMemoryPercentage) {
super(minFreeMemoryPercentage);
String vendor = System.getProperty("java.vm.vendor");
isIbmJvm = vendor.toLowerCase(Locale.ROOT).startsWith("ibm corporation");
}
@Override
protected long[] getTotalAndFreeMemory() throws Exception {
ObjectName objectName = new ObjectName("java.lang:type=OperatingSystem");
List<Attribute> list = ManagementFactory.getPlatformMBeanServer().getAttributes(
objectName,
new String[] { isIbmJvm ? "TotalPhysicalMemory" : "TotalPhysicalMemorySize", "FreePhysicalMemorySize" })
.asList();
long total = ((Number) list.get(0).getValue()).longValue();
long free = ((Number) list.get(1).getValue()).longValue();
return new long[] { total, free };
}
}
}