package mineplex.servermonitor; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.logging.FileHandler; import java.util.logging.Logger; import mineplex.serverdata.DedicatedServer; import mineplex.serverdata.DedicatedServerSorter; import mineplex.serverdata.MinecraftServer; import mineplex.serverdata.Region; import mineplex.serverdata.ServerGroup; import mineplex.serverdata.ServerManager; import mineplex.serverdata.ServerRepository; public class ServerMonitor { private static ServerRepository _repository = null; private static int _count = 0; private static HashSet _processes = new HashSet(); private static HashMap _badServers = new HashMap(); private static SimpleDateFormat _dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); private static Logger _logger = Logger.getLogger("ServerMonitor"); public static void main (String args[]) { Region region = !new File("eu.dat").exists() ? Region.US : Region.EU; _repository = ServerManager.getServerRepository(region); // Fetches and connects to server repo File logFile = new File("monitor.log"); if (!logFile.exists()) { try { logFile.createNewFile(); } catch (IOException e1) { e1.printStackTrace(); } } try { FileHandler fileHandler = new FileHandler("monitor.log"); fileHandler.setFormatter(new CustomFormatter()); _logger.addHandler(fileHandler); _logger.setUseParentHandlers(false); } catch (SecurityException | IOException e1) { e1.printStackTrace(); } HashMap> serverTracker = new HashMap>(); while (true) { Collection serverGroups = _repository.getServerGroups(); for (MinecraftServer deadServer : _repository.getDeadServers()) { killServer(deadServer.getName(), deadServer.getPublicAddress(), true); } List dedicatedServers = new ArrayList(_repository.getDedicatedServers()); if (_count % 15 == 0) { _badServers.clear(); for (DedicatedServer serverData : dedicatedServers) { if (isServerOffline(serverData)) { System.out.println("------=[OFFLINE]=------=[" + serverData.getName() + ":" + serverData.getPublicAddress() + "]=------=[OFFLINE]=------"); _badServers.put(serverData.getName(), true); } } System.out.println(_badServers.size() + " bad servers."); } for (Iterator iterator = dedicatedServers.iterator(); iterator.hasNext();) { DedicatedServer serverData = iterator.next(); if (_badServers.containsKey(serverData.getName())) iterator.remove(); } // TODO: Check with Jonathan to see if we still need this duplication server code /*for (GroupStatusData groupStatus : groupStatusList.values()) { for (ServerStatusData serverToKill : groupStatus.KillServers) { System.out.println("----DUPLICATE SERVER----> " + serverToKill.Address + ", " + serverToKill.Name); killServer(serverToKill); } for (ServerStatusData serverToKill : groupStatus.Servers.values()) { if (serverTracker.containsKey(serverToKill.Name)) serverTracker.remove(serverToKill.Name); } }*/ HashSet onlineServers = new HashSet(); for (MinecraftServer minecraftServer : _repository.getServerStatuses()) { onlineServers.add(minecraftServer.getName()); } for (Iterator>> iterator = serverTracker.entrySet().iterator(); iterator.hasNext();) { Entry> entry = iterator.next(); if (onlineServers.contains(entry.getKey())) iterator.remove(); else if (System.currentTimeMillis() - entry.getValue().getValue() > 20000) { System.out.println("-=[SERVER STARTUP TOO SLOW]=- " + entry.getKey()); String serverName = entry.getKey(); String serverAddress = entry.getValue().getKey(); killServer(serverName, serverAddress, true); iterator.remove(); } } for (ServerGroup serverGroup : serverGroups) { handleGroupChanges(dedicatedServers, serverTracker, serverGroup, false); } int processWaits = 0; while (_processes.size() > 0) { for (Iterator iterator = _processes.iterator(); iterator.hasNext();) { ProcessRunner pr = iterator.next(); try { pr.join(100); } catch (InterruptedException e) { e.printStackTrace(); } if (pr.isDone()) iterator.remove(); } if (_processes.size() > 0) { try { System.out.println("Sleeping while processes run..."); Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } } if (processWaits >= 10) { System.out.println("Killing stale processes."); for (Iterator iterator = _processes.iterator(); iterator.hasNext();) { iterator.next().abort(); iterator.remove(); } } processWaits++; } processWaits = 0; try { System.out.println("Natural sleep."); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } _count++; } } private static void handleGroupChanges(List dedicatedServers, HashMap> serverTracker, ServerGroup serverGroup, boolean free) { int serverNum = 0; //GroupStatusData groupStatus = groupStatusList.get(serverGroup.Name); int requiredTotal = serverGroup.getRequiredTotalServers(); int requiredJoinable = serverGroup.getRequiredJoinableServers(); int joinableServers = serverGroup.getJoinableCount(); int totalServers = serverGroup.getServerCount(); int serversToAdd = Math.max(requiredTotal - totalServers, requiredJoinable - joinableServers); int serversToKill = (totalServers > requiredTotal && joinableServers > requiredJoinable) ? Math.min(joinableServers - requiredJoinable, serverGroup.getEmptyServers().size()) : 0; // Minimum 1500 slot bufferzone if (serverGroup.getName().equalsIgnoreCase("Lobby")) { if (serverGroup.getMaxPlayerCount() - serverGroup.getPlayerCount() < 1500) serversToAdd = requiredJoinable; } while (serversToAdd > 0) { serverNum = serverGroup.generateUniqueId(serverNum + 1); Collections.sort(dedicatedServers, new DedicatedServerSorter()); DedicatedServer bestServer = getBestDedicatedServer(dedicatedServers, serverGroup); if (bestServer == null) { System.out.println("No best dynamic server available for group " + serverGroup.getName()); break; } if (serverTracker.containsKey(serverGroup.getPrefix() + "-" + serverNum)) System.out.println("[WAITING] On " + serverGroup.getPrefix() + "-" + serverNum + " to finish starting..."); else { startServer(bestServer, serverGroup, serverNum, free); serverTracker.put(serverGroup.getPrefix() + "-" + serverNum, new AbstractMap.SimpleEntry(bestServer.getPublicAddress(), System.currentTimeMillis())); } serversToAdd--; } while (serversToKill > 0) { List emptyServers = new ArrayList(serverGroup.getEmptyServers()); MinecraftServer emptyServer = emptyServers.get(serversToKill - 1); System.out.println("[" + emptyServer.getName() + ":" + emptyServer.getPublicAddress() + "] Killing " + serverGroup.getName() + " Req Total: " + serverGroup.getRequiredTotalServers() + " Req Joinable: " + serverGroup.getRequiredJoinableServers() + " | Actual Total: " + serverGroup.getServerCount() + " Actual Joinable: " + serverGroup.getJoinableCount()); killServer(emptyServer); serversToKill--; } } private static void killServer(final String serverName, final String serverAddress, final boolean announce) { String cmd = "/home/mineplex/easyRemoteKillServer.sh"; ProcessRunner pr = new ProcessRunner(new String[] {"/bin/sh", cmd, serverAddress, serverName}); pr.start(new GenericRunnable() { public void run(Boolean error) { if (!error) { MinecraftServer server = _repository.getServerStatus(serverName); if (server != null) { _repository.removeServerStatus(server); } } if (announce) { if (error) System.out.println("[" + serverName + ":" + serverAddress + "] Kill errored."); else System.out.println("Sent kill command to " + serverAddress + " for " + serverName + " completed"); } } }); try { pr.join(500); } catch (InterruptedException e1) { e1.printStackTrace(); } if (!pr.isDone()) _processes.add(pr); } private static boolean isServerOffline(DedicatedServer serverData) { boolean success = false; Process process = null; String cmd = "/home/mineplex/isServerOnline.sh"; ProcessBuilder processBuilder = new ProcessBuilder(new String[] {"/bin/sh", cmd, serverData.getPublicAddress()}); try { process = processBuilder.start(); process.waitFor(); BufferedReader reader=new BufferedReader(new InputStreamReader(process.getInputStream())); String line = reader.readLine(); while(line != null) { success = line.equals("Success"); line=reader.readLine(); } } catch (Exception e1) { e1.printStackTrace(); } finally { process.destroy(); } return !success; } private static DedicatedServer getBestDedicatedServer(Collection dedicatedServers, ServerGroup serverGroup) { DedicatedServer bestServer = null; for (DedicatedServer serverData : dedicatedServers) { if (serverData.getAvailableRam() > serverGroup.getRequiredRam() && serverData.getAvailableCpu() > serverGroup.getRequiredCpu()) { if (bestServer == null || serverData.getServerCount(serverGroup) < bestServer.getServerCount(serverGroup)) { bestServer = serverData; } } } return bestServer; } private static void killServer(final MinecraftServer serverToKill) { killServer(serverToKill.getName(), serverToKill.getPublicAddress(), true); } private static void startServer(final DedicatedServer serverSpace, final ServerGroup serverGroup, final int serverNum, final boolean free) { String cmd = "/home/mineplex/easyRemoteStartServerCustom.sh"; final String groupPrefix = serverGroup.getPrefix(); final String serverName = serverSpace.getName(); final String serverAddress = serverSpace.getPublicAddress(); ProcessRunner pr = new ProcessRunner(new String[] {"/bin/sh", cmd, serverAddress, serverSpace.getPrivateAddress(), (serverGroup.getPortSection() + serverNum) + "", serverGroup.getRequiredRam() + "", serverGroup.getWorldZip(), serverGroup.getPlugin(), serverGroup.getConfigPath(), serverGroup.getName(), serverGroup.getPrefix() + "-" + serverNum, serverGroup.getMinPlayers() + "", serverGroup.getMaxPlayers() + "", serverGroup.getPvp() + "", serverGroup.getTournament() + "", free + "", serverSpace.isUsRegion() ? "true" : "false", serverGroup.getArcadeGroup() + "", serverGroup.getGames(), serverGroup.getServerType(), serverGroup.getAddNoCheat() + ""}); pr.start(new GenericRunnable() { public void run(Boolean error) { if (error) System.out.println("[" + serverName + ":" + serverAddress + "] Errored " + serverName + "(" + groupPrefix+ "-" + serverNum + (free ? "-FREE" : "") + ")"); else System.out.println("[" + serverName + ":" + serverAddress + "] Added " + serverName + "(" + groupPrefix+ "-" + serverNum + (free ? "-FREE" : "") + ")"); } }); try { pr.join(500); } catch (InterruptedException e1) { e1.printStackTrace(); } serverSpace.incrementServerCount(serverGroup); if (!pr.isDone()) _processes.add(pr); } private static void log(String message) { _logger.info("[" + _dateFormat.format(new Date()) + "] " + message); } }