112 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			112 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.server.dedicated;
 | |
| 
 | |
| import com.google.common.collect.Streams;
 | |
| import com.mojang.logging.LogUtils;
 | |
| import java.lang.management.ManagementFactory;
 | |
| import java.lang.management.ThreadInfo;
 | |
| import java.lang.management.ThreadMXBean;
 | |
| import java.nio.file.Path;
 | |
| import java.util.Locale;
 | |
| import java.util.Timer;
 | |
| import java.util.TimerTask;
 | |
| import java.util.stream.Collectors;
 | |
| import net.minecraft.CrashReport;
 | |
| import net.minecraft.CrashReportCategory;
 | |
| import net.minecraft.CrashReportDetail;
 | |
| import net.minecraft.ReportType;
 | |
| import net.minecraft.Util;
 | |
| import net.minecraft.server.Bootstrap;
 | |
| import net.minecraft.util.TimeUtil;
 | |
| import net.minecraft.world.level.GameRules;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| public class ServerWatchdog implements Runnable {
 | |
| 	private static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	private static final long MAX_SHUTDOWN_TIME = 10000L;
 | |
| 	private static final int SHUTDOWN_STATUS = 1;
 | |
| 	private final DedicatedServer server;
 | |
| 	private final long maxTickTimeNanos;
 | |
| 
 | |
| 	public ServerWatchdog(DedicatedServer server) {
 | |
| 		this.server = server;
 | |
| 		this.maxTickTimeNanos = server.getMaxTickLength() * TimeUtil.NANOSECONDS_PER_MILLISECOND;
 | |
| 	}
 | |
| 
 | |
| 	public void run() {
 | |
| 		while (this.server.isRunning()) {
 | |
| 			long l = this.server.getNextTickTime();
 | |
| 			long m = Util.getNanos();
 | |
| 			long n = m - l;
 | |
| 			if (n > this.maxTickTimeNanos) {
 | |
| 				LOGGER.error(
 | |
| 					LogUtils.FATAL_MARKER,
 | |
| 					"A single server tick took {} seconds (should be max {})",
 | |
| 					String.format(Locale.ROOT, "%.2f", (float)n / (float)TimeUtil.NANOSECONDS_PER_SECOND),
 | |
| 					String.format(Locale.ROOT, "%.2f", this.server.tickRateManager().millisecondsPerTick() / (float)TimeUtil.MILLISECONDS_PER_SECOND)
 | |
| 				);
 | |
| 				LOGGER.error(LogUtils.FATAL_MARKER, "Considering it to be crashed, server will forcibly shutdown.");
 | |
| 				CrashReport crashReport = createWatchdogCrashReport("Watching Server", this.server.getRunningThread().threadId());
 | |
| 				this.server.fillSystemReport(crashReport.getSystemReport());
 | |
| 				CrashReportCategory crashReportCategory = crashReport.addCategory("Performance stats");
 | |
| 				crashReportCategory.setDetail(
 | |
| 					"Random tick rate", (CrashReportDetail<String>)(() -> this.server.getWorldData().getGameRules().getRule(GameRules.RULE_RANDOMTICKING).toString())
 | |
| 				);
 | |
| 				crashReportCategory.setDetail(
 | |
| 					"Level stats",
 | |
| 					(CrashReportDetail<String>)(() -> (String)Streams.stream(this.server.getAllLevels())
 | |
| 						.map(serverLevel -> serverLevel.dimension().location() + ": " + serverLevel.getWatchdogStats())
 | |
| 						.collect(Collectors.joining(",\n")))
 | |
| 				);
 | |
| 				Bootstrap.realStdoutPrintln("Crash report:\n" + crashReport.getFriendlyReport(ReportType.CRASH));
 | |
| 				Path path = this.server.getServerDirectory().resolve("crash-reports").resolve("crash-" + Util.getFilenameFormattedDateTime() + "-server.txt");
 | |
| 				if (crashReport.saveToFile(path, ReportType.CRASH)) {
 | |
| 					LOGGER.error("This crash report has been saved to: {}", path.toAbsolutePath());
 | |
| 				} else {
 | |
| 					LOGGER.error("We were unable to save this crash report to disk.");
 | |
| 				}
 | |
| 
 | |
| 				this.exit();
 | |
| 			}
 | |
| 
 | |
| 			try {
 | |
| 				Thread.sleep((l + this.maxTickTimeNanos - m) / TimeUtil.NANOSECONDS_PER_MILLISECOND);
 | |
| 			} catch (InterruptedException var10) {
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static CrashReport createWatchdogCrashReport(String title, long threadId) {
 | |
| 		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
 | |
| 		ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true);
 | |
| 		StringBuilder stringBuilder = new StringBuilder();
 | |
| 		Error error = new Error("Watchdog");
 | |
| 
 | |
| 		for (ThreadInfo threadInfo : threadInfos) {
 | |
| 			if (threadInfo.getThreadId() == threadId) {
 | |
| 				error.setStackTrace(threadInfo.getStackTrace());
 | |
| 			}
 | |
| 
 | |
| 			stringBuilder.append(threadInfo);
 | |
| 			stringBuilder.append("\n");
 | |
| 		}
 | |
| 
 | |
| 		CrashReport crashReport = new CrashReport(title, error);
 | |
| 		CrashReportCategory crashReportCategory = crashReport.addCategory("Thread Dump");
 | |
| 		crashReportCategory.setDetail("Threads", stringBuilder);
 | |
| 		return crashReport;
 | |
| 	}
 | |
| 
 | |
| 	private void exit() {
 | |
| 		try {
 | |
| 			Timer timer = new Timer();
 | |
| 			timer.schedule(new TimerTask() {
 | |
| 				public void run() {
 | |
| 					Runtime.getRuntime().halt(1);
 | |
| 				}
 | |
| 			}, 10000L);
 | |
| 			System.exit(1);
 | |
| 		} catch (Throwable var2) {
 | |
| 			Runtime.getRuntime().halt(1);
 | |
| 		}
 | |
| 	}
 | |
| }
 |