package org.xbib.elasticsearch.common;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.xbib.elasticsearch.common.jvm.GarbageCollector;
import org.xbib.elasticsearch.common.jvm.JvmInfo;
import org.xbib.elasticsearch.common.jvm.MemoryPool;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.common.unit.TimeValue.timeValueSeconds;
public class GcMonitor {
private final static ESLogger logger = ESLoggerFactory.getLogger(GcMonitor.class.getName());
private final boolean enabled;
private final Map<String, GcThreshold> gcThresholds;
private volatile ScheduledFuture<?> scheduledFuture;
public GcMonitor(Settings settings) {
this.enabled = settings.getAsBoolean("monitor.gc.enabled", false);
TimeValue interval = settings.getAsTime("monitor.gc.interval", timeValueSeconds(1));
this.gcThresholds = new HashMap<>();
Map<String, Settings> gcThresholdGroups = settings.getGroups("monitor.gc.level");
for (Map.Entry<String, Settings> entry : gcThresholdGroups.entrySet()) {
String name = entry.getKey();
TimeValue warn = entry.getValue().getAsTime("warn", null);
TimeValue info = entry.getValue().getAsTime("info", null);
TimeValue debug = entry.getValue().getAsTime("debug", null);
if (warn == null || info == null || debug == null) {
logger.warn("ignoring gc_threshold for [{}], missing warn/info/debug values", name);
} else {
gcThresholds.put(name, new GcThreshold(name, warn.millis(), info.millis(), debug.millis()));
}
}
if (!gcThresholds.containsKey(JvmInfo.YOUNG)) {
gcThresholds.put(JvmInfo.YOUNG, new GcThreshold(JvmInfo.YOUNG, 1000, 700, 400));
}
if (!gcThresholds.containsKey(JvmInfo.OLD)) {
gcThresholds.put(JvmInfo.OLD, new GcThreshold(JvmInfo.OLD, 10000, 5000, 2000));
}
if (!gcThresholds.containsKey("default")) {
gcThresholds.put("default", new GcThreshold("default", 10000, 5000, 2000));
}
logger.debug("enabled [{}], interval [{}], gc_threshold [{}]", enabled, interval, this.gcThresholds);
if (enabled) {
scheduledFuture = Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(new GcMonitorThread(), 0L, interval.seconds(), TimeUnit.SECONDS);
}
}
public void close() {
if (!enabled) {
return;
}
if (scheduledFuture != null) {
scheduledFuture.cancel(false);
}
}
private class GcThreshold {
public final String name;
public final long warnThreshold;
public final long infoThreshold;
public final long debugThreshold;
GcThreshold(String name, long warnThreshold, long infoThreshold, long debugThreshold) {
this.name = name;
this.warnThreshold = warnThreshold;
this.infoThreshold = infoThreshold;
this.debugThreshold = debugThreshold;
}
@Override
public String toString() {
return "GcThreshold{" +
"name='" + name + '\'' +
", warnThreshold=" + warnThreshold +
", infoThreshold=" + infoThreshold +
", debugThreshold=" + debugThreshold +
'}';
}
}
private class GcMonitorThread implements Runnable {
private JvmInfo jvmInfo = JvmInfo.getInstance();
private JvmInfo.Stats lastJvmStats = jvmInfo.stats;
private long seq = 0;
public GcMonitorThread() {
}
@Override
public void run() {
try {
monitorGc();
} catch (Throwable t) {
logger.debug("failed to monitor", t);
}
}
private synchronized void monitorGc() {
seq++;
JvmInfo.Stats currentJvmStats = JvmInfo.readStats();
for (int i = 0; i < currentJvmStats.getGc().getCollectors().length; i++) {
GarbageCollector gc = currentJvmStats.getGc().getCollectors()[i];
GarbageCollector prevGc = lastJvmStats.getGc().getCollectors()[i];
long collections = gc.getCollectionCount() - prevGc.getCollectionCount();
if (collections == 0) {
continue;
}
long collectionTime = gc.getCollectionTime().getMillis() - prevGc.getCollectionTime().getMillis();
if (collectionTime == 0) {
continue;
}
GcThreshold gcThreshold = gcThresholds.get(gc.getName());
if (gcThreshold == null) {
gcThreshold = gcThresholds.get("default");
}
long avgCollectionTime = collectionTime / collections;
if (avgCollectionTime > gcThreshold.warnThreshold) {
logger.warn("[gc][{}][{}][{}] duration [{}], collections [{}]/[{}], total [{}]/[{}], memory [{}]->[{}]/[{}], all_pools {}",
gc.getName(),
seq,
gc.getCollectionCount(),
TimeValue.timeValueMillis(collectionTime),
collections,
TimeValue.timeValueMillis(currentJvmStats.getTimestamp() - lastJvmStats.getTimestamp()),
TimeValue.timeValueMillis(collectionTime),
gc.getCollectionTime(),
lastJvmStats.getMem().getHeapUsed(),
currentJvmStats.getMem().getHeapUsed(),
currentJvmStats.getMem().getHeapMax(),
buildPools(lastJvmStats, currentJvmStats));
} else if (avgCollectionTime > gcThreshold.infoThreshold) {
logger.info("[gc][{}][{}][{}] duration [{}], collections [{}]/[{}], total [{}]/[{}], memory [{}]->[{}]/[{}], all_pools {}",
gc.getName(),
seq,
gc.getCollectionCount(),
TimeValue.timeValueMillis(collectionTime),
collections,
TimeValue.timeValueMillis(currentJvmStats.getTimestamp() - lastJvmStats.getTimestamp()),
TimeValue.timeValueMillis(collectionTime),
gc.getCollectionTime(),
lastJvmStats.getMem().getHeapUsed(),
currentJvmStats.getMem().getHeapUsed(),
currentJvmStats.getMem().getHeapMax(),
buildPools(lastJvmStats, currentJvmStats));
} else if (avgCollectionTime > gcThreshold.debugThreshold && logger.isDebugEnabled()) {
logger.debug("[gc][{}][{}][{}] duration [{}], collections [{}]/[{}], total [{}]/[{}], memory [{}]->[{}]/[{}], all_pools {}",
gc.getName(),
seq,
gc.getCollectionCount(),
TimeValue.timeValueMillis(collectionTime),
collections,
TimeValue.timeValueMillis(currentJvmStats.getTimestamp() - lastJvmStats.getTimestamp()),
TimeValue.timeValueMillis(collectionTime),
gc.getCollectionTime(),
lastJvmStats.getMem().getHeapUsed(),
currentJvmStats.getMem().getHeapUsed(),
currentJvmStats.getMem().getHeapMax(),
buildPools(lastJvmStats, currentJvmStats));
}
}
lastJvmStats = currentJvmStats;
}
private String buildPools(JvmInfo.Stats prev, JvmInfo.Stats current) {
StringBuilder sb = new StringBuilder();
for (MemoryPool currentPool : current.getMem()) {
MemoryPool prevPool = null;
for (MemoryPool pool : prev.getMem()) {
if (pool.getName().equals(currentPool.getName())) {
prevPool = pool;
break;
}
}
sb.append("{[").append(currentPool.getName())
.append("] [").append(prevPool == null ? "?" : prevPool.getUsed()).append("]->[").append(currentPool.getUsed()).append("]/[").append(currentPool.getMax()).append("]}");
}
return sb.toString();
}
}
}