Added RestartCommand and SuicideCommand

ServerMonitor will now cleanup bloated Lobbies starting from the highest number lobby.

Fixed issue with new servers getting cleaned up as excess as soon as they started.
This commit is contained in:
Jonathan Williams 2015-01-22 23:19:40 -08:00
parent 08078c1622
commit 73866669b8
15 changed files with 478 additions and 41 deletions

View File

@ -0,0 +1,6 @@
package mineplex.core.common;
public interface GenericRunnable<T>
{
void run(T t);
}

View File

@ -0,0 +1,81 @@
package mineplex.core.common;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ProcessRunner extends Thread
{
private ProcessBuilder _processBuilder;
private Process _process;
private GenericRunnable<Boolean> _runnable;
boolean _done = false;
Boolean _error = false;
public ProcessRunner(String[] args)
{
super("ProcessRunner " + args);
_processBuilder = new ProcessBuilder(args);
}
public void run()
{
try
{
_process = _processBuilder.start();
_process.waitFor();
BufferedReader reader=new BufferedReader(new InputStreamReader(_process.getInputStream()));
String line = reader.readLine();
while(line != null)
{
if (line.equals("255"))
_error = true;
line=reader.readLine();
}
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
finally
{
_done = true;
if (_runnable != null)
_runnable.run(_error);
}
}
public void start(GenericRunnable<Boolean> runnable)
{
super.start();
_runnable = runnable;
}
public int exitValue() throws IllegalStateException
{
if (_process != null)
{
return _process.exitValue();
}
throw new IllegalStateException("Process not started yet");
}
public boolean isDone()
{
return _done;
}
public void abort()
{
if (!isDone())
{
_process.destroy();
}
}
}

View File

@ -10,6 +10,8 @@ import org.bukkit.plugin.java.JavaPlugin;
import mineplex.core.MiniPlugin;
import mineplex.core.account.CoreClientManager;
import mineplex.core.common.GenericRunnable;
import mineplex.core.common.ProcessRunner;
import mineplex.core.common.util.Callback;
import mineplex.core.monitor.LagMeter;
import mineplex.core.updater.UpdateType;
@ -21,6 +23,7 @@ import mineplex.serverdata.ServerGroup;
import mineplex.serverdata.ServerManager;
import mineplex.serverdata.ServerRepository;
import mineplex.serverdata.Utility;
import mineplex.serverdata.transfers.SuicideCommand;
public class ServerStatusManager extends MiniPlugin
{
@ -32,7 +35,7 @@ public class ServerStatusManager extends MiniPlugin
private LagMeter _lagMeter;
private String _name;
private boolean _us;
private Region _region;
private boolean _enabled = true;
@ -53,12 +56,12 @@ public class ServerStatusManager extends MiniPlugin
_name = plugin.getConfig().getString("serverstatus.name");
_region = plugin.getConfig().getBoolean("serverstatus.us") ? Region.US : Region.EU;
ServerCommandManager.getInstance().initializeServer(_name);
ServerCommandManager.getInstance().registerCommandType("SuicideCommand", SuicideCommand.class, new SuicideHandler(this, _name, _region));
_us = plugin.getConfig().getBoolean("serverstatus.us");
Region region = _us ? Region.US : Region.EU;
_repository = ServerManager.getServerRepository(region);
_repository = ServerManager.getServerRepository(_region);
saveServerStatus();
}
@ -132,7 +135,25 @@ public class ServerStatusManager extends MiniPlugin
{
public void run()
{
_repository.updataServerStatus(serverSnapshot, DEFAULT_SERVER_TIMEOUT);
MinecraftServer server = _repository.getServerStatus(serverSnapshot.getName());
int timeout = DEFAULT_SERVER_TIMEOUT;
if (server != null && server.getPublicAddress().equalsIgnoreCase(serverSnapshot.getPublicAddress()))
{
ProcessRunner pr = new ProcessRunner(new String[] {"/bin/sh", "/home/mineplex/config/killServer.sh", serverSnapshot.getName()});
pr.start(new GenericRunnable<Boolean>()
{
public void run(Boolean error)
{
if (error)
log("Error Killing myself.");
else
log("It worked.");
}
});
}
_repository.updataServerStatus(serverSnapshot, timeout);
}
});
}
@ -146,7 +167,7 @@ public class ServerStatusManager extends MiniPlugin
ServerListPingEvent event = new ServerListPingEvent(null, GetPlugin().getServer().getMotd(), GetPlugin().getServer().getOnlinePlayers().size(), GetPlugin().getServer().getMaxPlayers());
GetPluginManager().callEvent(event);
String motd = event.getMotd();
String motd = _enabled ? event.getMotd() : "Restarting";
int playerCount = _clientManager.getPlayerCountIncludingConnecting();
int maxPlayerCount = event.getMaxPlayers();
int tps = (int) _lagMeter.getTicksPerSecond();
@ -165,11 +186,6 @@ public class ServerStatusManager extends MiniPlugin
return _name;
}
public boolean getUs()
{
return _us;
}
public void retrieveServerGroups(final Callback<Collection<ServerGroup>> callback)
{
if (!_enabled)
@ -186,4 +202,15 @@ public class ServerStatusManager extends MiniPlugin
}
});
}
public Region getRegion()
{
return _region;
}
public void disableStatus()
{
_enabled = false;
saveServerStatus();
}
}

View File

@ -0,0 +1,52 @@
package mineplex.core.status;
import mineplex.core.common.util.F;
import mineplex.core.portal.Portal;
import mineplex.serverdata.CommandCallback;
import mineplex.serverdata.Region;
import mineplex.serverdata.ServerCommand;
import mineplex.serverdata.transfers.SuicideCommand;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class SuicideHandler implements CommandCallback
{
private ServerStatusManager _statusManager;
private String _serverName;
private Region _region;
public SuicideHandler(ServerStatusManager statusManager, String serverName, Region region)
{
_statusManager = statusManager;
_serverName = serverName;
_region = region;
}
public void run(ServerCommand command)
{
if (command instanceof SuicideCommand)
{
String serverName = ((SuicideCommand)command).getServerName();
Region region = ((SuicideCommand)command).getRegion();
if (!serverName.equalsIgnoreCase(_serverName) || _region != region)
return;
for (Player player : Bukkit.getOnlinePlayers())
{
player.sendMessage(F.main("Cleanup", "Server is being cleaned up, you're being sent to a lobby."));
}
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(Bukkit.getPluginManager().getPlugins()[0], new Runnable()
{
public void run()
{
Portal.getInstance().sendAllPlayers("Lobby");
}
}, 60L);
_statusManager.disableStatus();
}
}
}

View File

@ -14,30 +14,48 @@ import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.plugin.java.JavaPlugin;
import mineplex.core.MiniPlugin;
import mineplex.core.common.util.C;
import mineplex.core.common.util.F;
import mineplex.core.common.util.NautHashMap;
import mineplex.core.portal.Portal;
import mineplex.core.updater.event.RestartServerEvent;
import mineplex.core.updater.event.UpdateEvent;
import mineplex.serverdata.Region;
import mineplex.serverdata.ServerCommandManager;
import mineplex.serverdata.transfers.RestartCommand;
public class FileUpdater extends MiniPlugin
{
private Portal _portal;
private NautHashMap<String, String> _jarMd5Map = new NautHashMap<String, String>();
private String _serverName;
private Region _region;
private boolean _needUpdate;
private boolean _enabled = true;
public FileUpdater(JavaPlugin plugin, Portal portal)
public FileUpdater(JavaPlugin plugin, Portal portal, String serverName, Region region)
{
super("File Updater", plugin);
_portal = portal;
_serverName = serverName;
_region = region;
GetPluginMd5s();
if (new File("IgnoreUpdates.dat").exists())
_enabled = false;
// Register the server command type for future use
ServerCommandManager.getInstance().registerCommandType("RestartCommand", RestartCommand.class, new RestartHandler(plugin, _serverName, _region));
}
@Override
public void AddCommands()
{
addCommand(new RestartServerCommand(this));
}
@EventHandler
@ -57,7 +75,7 @@ public class FileUpdater extends MiniPlugin
{
for (Player player : Bukkit.getOnlinePlayers())
{
player.sendMessage(F.main("Updater", "Server is restarting for an update."));
player.sendMessage(F.main("Updater", C.cGold + _serverName + C.cGray + " is restarting for an update."));
}
GetPlugin().getServer().getScheduler().scheduleSyncDelayedTask(GetPlugin(), new Runnable()
@ -205,4 +223,9 @@ public class FileUpdater extends MiniPlugin
}
}
}
public Region getRegion()
{
return _region;
}
}

View File

@ -0,0 +1,76 @@
package mineplex.core.updater;
import mineplex.core.common.util.F;
import mineplex.core.portal.Portal;
import mineplex.serverdata.CommandCallback;
import mineplex.serverdata.Region;
import mineplex.serverdata.ServerCommand;
import mineplex.serverdata.transfers.RestartCommand;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class RestartHandler implements CommandCallback, Listener
{
private JavaPlugin _plugin;
private String _serverName;
private Region _region;
private boolean _restarting;
public RestartHandler(JavaPlugin plugin, String serverName, Region region)
{
_plugin = plugin;
_serverName = serverName;
_region = region;
_plugin.getServer().getPluginManager().registerEvents(this, _plugin);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void reflectMotd(ServerListPingEvent event)
{
if (_restarting)
event.setMotd("Restarting soon");
}
public void run(ServerCommand command)
{
if (command instanceof RestartCommand)
{
String serverName = ((RestartCommand)command).getServerName();
Region region = ((RestartCommand)command).getRegion();
if (!serverName.equalsIgnoreCase(_serverName) || _region != region)
return;
_restarting = true;
for (Player player : Bukkit.getOnlinePlayers())
{
player.sendMessage(F.main("Restart", "Server is restarting, you're being sent to a lobby."));
}
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(Bukkit.getPluginManager().getPlugins()[0], new Runnable()
{
public void run()
{
Portal.getInstance().sendAllPlayers("Lobby");
}
}, 60L);
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(Bukkit.getPluginManager().getPlugins()[0], new Runnable()
{
public void run()
{
Bukkit.getServer().shutdown();
}
}, 100L);
}
}
}

View File

@ -0,0 +1,44 @@
package mineplex.core.updater;
import org.bukkit.entity.Player;
import mineplex.core.command.CommandBase;
import mineplex.core.common.Rank;
import mineplex.core.common.util.C;
import mineplex.core.common.util.Callback;
import mineplex.core.common.util.F;
import mineplex.core.common.util.UtilPlayer;
import mineplex.core.portal.Portal;
import mineplex.serverdata.transfers.RestartCommand;
public class RestartServerCommand extends CommandBase<FileUpdater>
{
public RestartServerCommand(FileUpdater plugin)
{
super(plugin, Rank.ADMIN, "restart");
}
@Override
public void Execute(final Player caller, final String[] args)
{
if (args == null || args.length < 1)
{
UtilPlayer.message(caller, F.main("Restart", "You must specify a server to restart."));
return;
}
Portal.getInstance().doesServerExist(args[0], new Callback<Boolean>()
{
public void run(Boolean serverExists)
{
if (serverExists)
{
new RestartCommand(args[0], Plugin.getRegion()).publish();
UtilPlayer.message(caller, F.main("Restart", "Sent restart command to " + C.cGold + args[0] + C.cGray + "."));
}
else
UtilPlayer.message(caller, F.main("Restart", C.cGold + args[0] + C.cGray + " doesn't exist."));
}
});
}
}

View File

@ -47,13 +47,13 @@ public class DedicatedServer
*/
public DedicatedServer(Map<String, String> data)
{
this._name = data.get("name");
this._publicAddress = data.get("publicAddress");
this._privateAddress = data.get("privateAddress");
this._region = Region.valueOf(data.get("region").toUpperCase());
this._availableCpu = Integer.valueOf(data.get("cpu"));
this._availableRam = Integer.valueOf(data.get("ram"));
this._serverCounts = new HashMap<String, Integer>();
_name = data.get("name");
_publicAddress = data.get("publicAddress");
_privateAddress = data.get("privateAddress");
_region = Region.valueOf(data.get("region").toUpperCase());
_availableCpu = Integer.valueOf(data.get("cpu"));
_availableRam = Integer.valueOf(data.get("ram"));
_serverCounts = new HashMap<String, Integer>();
}
/**

View File

@ -87,7 +87,7 @@ public class MinecraftServer
*/
public double getUptime()
{
return (System.currentTimeMillis() - _startUpDate) / 1000d;
return (System.currentTimeMillis() / 1000d - _startUpDate);
}
/**
@ -111,4 +111,8 @@ public class MinecraftServer
{
_group = group;
}
public void setName(String name)
{
_name = name;
}
}

View File

@ -295,6 +295,7 @@ public class RedisServerRepository implements ServerRepository
}
catch (Exception exception)
{
System.out.println("Error parsing ServerGroup : " + data.get("name"));
exception.printStackTrace();
}
}

View File

@ -3,7 +3,6 @@ package mineplex.serverdata;
import java.util.HashMap;
import java.util.Map;
import mineplex.serverdata.transfers.TransferCommand;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@ -33,9 +32,6 @@ public class ServerCommandManager
_commandTypes = new HashMap<String, CommandType>();
initialize();
// Register default command types
//registerCommandType("TransferCommand", TransferCommand.class);
}
/**

View File

@ -0,0 +1,25 @@
package mineplex.serverdata.transfers;
import mineplex.serverdata.Region;
import mineplex.serverdata.ServerCommand;
public class RestartCommand extends ServerCommand
{
private String _server;
public String getServerName() { return _server; }
private Region _region;
public Region getRegion() { return _region; }
public RestartCommand(String server, Region region)
{
_server = server;
_region = region;
}
@Override
public void run()
{
// Utilitizes a callback functionality to seperate dependencies
}
}

View File

@ -0,0 +1,25 @@
package mineplex.serverdata.transfers;
import mineplex.serverdata.Region;
import mineplex.serverdata.ServerCommand;
public class SuicideCommand extends ServerCommand
{
private String _server;
public String getServerName() { return _server; }
private Region _region;
public Region getRegion() { return _region; }
public SuicideCommand(String server, Region region)
{
_server = server;
_region = region;
}
@Override
public void run()
{
// Utilitizes a callback functionality to seperate dependencies
}
}

View File

@ -27,10 +27,11 @@ import mineplex.serverdata.Region;
import mineplex.serverdata.ServerGroup;
import mineplex.serverdata.ServerManager;
import mineplex.serverdata.ServerRepository;
import mineplex.serverdata.transfers.RestartCommand;
import mineplex.serverdata.transfers.SuicideCommand;
public class ServerMonitor
{
private static ServerRepository _repository = null;
private static int _count = 0;
private static HashSet<ProcessRunner> _processes = new HashSet<ProcessRunner>();
@ -40,6 +41,7 @@ public class ServerMonitor
private static Map<String, ServerGroup> _serverGroupMap = null;
private static List<DedicatedServer> _dedicatedServers = null;
private static HashSet<String> _deadServers = new HashSet<String>();
private static HashSet<String> _laggyServers = new HashSet<String>();
private static SimpleDateFormat _dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
private static Logger _logger = Logger.getLogger("ServerMonitor");
@ -54,7 +56,7 @@ public class ServerMonitor
_region = !new File("eu.dat").exists() ? Region.US : Region.EU;
_debug = new File("debug.dat").exists();
_repository = ServerManager.getServerRepository(_region); // Fetches and connects to server repo
File logFile = new File("monitor.log");
if (!logFile.exists())
@ -94,6 +96,17 @@ public class ServerMonitor
calculateTotalPlayers();
killDeadServers();
double totalCPU = 0.0;
double totalRAM = 0.0;
double availableCPU = 0.0;
double availableRAM = 0.0;
for (DedicatedServer server : _dedicatedServers)
{
totalCPU += server.getAvailableCpu();
totalRAM += server.getAvailableRam();
}
for (MinecraftServer minecraftServer : _serverStatuses)
{
if (minecraftServer.getMotd().contains("Finished") || (minecraftServer.getGroup().equalsIgnoreCase("UltraHardcore") && minecraftServer.getMotd().contains("Restarting") && minecraftServer.getPlayerCount() == 0))
@ -113,9 +126,6 @@ public class ServerMonitor
}
}
}
double totalCPU = 0.0;
double totalRAM = 0.0;
if (_count % 15 == 0)
{
@ -128,14 +138,6 @@ public class ServerMonitor
log("------=[OFFLINE]=------=[" + serverData.getName() + ":" + serverData.getPublicAddress() + "]=------=[OFFLINE]=------");
_badServers.put(serverData.getName(), true);
}
else
{
totalCPU += serverData.getAvailableCpu();
totalRAM += serverData.getAvailableRam();
}
if (_debug)
log("Server : " + serverData.getName() + " CPU : " + serverData.getAvailableCpu() + " RAM : " + serverData.getAvailableRam());
}
log(_badServers.size() + " bad servers.");
@ -147,7 +149,14 @@ public class ServerMonitor
if (_badServers.containsKey(serverData.getName()))
iterator.remove();
else
{
availableCPU += serverData.getAvailableCpu();
availableRAM += serverData.getAvailableRam();
}
}
log("Using " + Math.round((1 - availableCPU / totalCPU) * 10000.0) / 100.0 + "% of available CPU (" + availableCPU + " Free) and " + Math.round((1 - availableRAM / totalRAM) * 10000.0) / 100.0 + "% of available RAM (" + availableRAM / 1000 + "GB Free)");
for (ServerGroup groupStatus : _serverGroups)
{
@ -182,13 +191,32 @@ public class ServerMonitor
}
HashSet<String> onlineServers = new HashSet<String>();
HashSet<String> laggyServers = new HashSet<String>();
laggyServers.addAll(_laggyServers);
_laggyServers.clear();
for (MinecraftServer minecraftServer : _serverStatuses)
{
onlineServers.add(minecraftServer.getName());
if (minecraftServer.getTps() <= 17)
log("[Performance] " + minecraftServer.getName() + ":" + minecraftServer.getPublicAddress() + "] Running poorly at " + minecraftServer.getTps() + " TPS");
{
if (minecraftServer.getTps() <= 10)
{
if (laggyServers.contains(minecraftServer.getName()))
{
new RestartCommand(minecraftServer.getName(), _region).publish();
log("[RESTART LAGGY] : " + minecraftServer.getName() + ":" + minecraftServer.getPublicAddress());
}
else
{
_laggyServers.add(minecraftServer.getName());
log("[IMPENDING RESTART LAGGY] : " + minecraftServer.getName() + ":" + minecraftServer.getPublicAddress());
}
}
else
log("[Performance] " + minecraftServer.getName() + ":" + minecraftServer.getPublicAddress() + "] Running poorly at " + minecraftServer.getTps() + " TPS");
}
}
for (Iterator<Entry<String, Entry<String, Long>>> iterator = serverTracker.entrySet().iterator(); iterator.hasNext();)
@ -286,6 +314,9 @@ public class ServerMonitor
_deadServers.clear();
for (MinecraftServer deadServer : _repository.getDeadServers())
{
if (deadServer.getUptime() <= 10)
continue;
if (deadServers.contains(deadServer.getName()))
{
killServer(deadServer.getName(), deadServer.getPublicAddress(), deadServer.getPlayerCount(), "[KILLED] [DEAD] " + deadServer.getName() + ":" + deadServer.getPublicAddress(), true);
@ -319,7 +350,7 @@ public class ServerMonitor
_totalPlayers += serverGroup.getPlayerCount();
}
System.out.println("Total Players : " + _totalPlayers);
log("Total Players : " + _totalPlayers);
}
private static void handleGroupChanges(HashMap<String, Entry<String, Long>> serverTracker, ServerGroup serverGroup, boolean free)
@ -331,6 +362,7 @@ public class ServerMonitor
int totalServers = serverGroup.getServerCount();
int serversToAdd = Math.max(0, Math.max(requiredTotal - totalServers, requiredJoinable - joinableServers));
int serversToKill = (totalServers > requiredTotal && joinableServers > requiredJoinable) ? Math.min(joinableServers - requiredJoinable, serverGroup.getEmptyServers().size()) : 0;
int serversToRestart = 0;
// Minimum 1500 slot bufferzone
if (serverGroup.getName().equalsIgnoreCase("Lobby"))
@ -344,6 +376,14 @@ public class ServerMonitor
}
else if (serversToKill > 0)
serversToKill = Math.min(serversToKill, (availableSlots - 1500) / 80);
else if (serversToAdd == 0 && joinableServers > requiredJoinable && totalServers > requiredTotal)
{
serversToRestart = Math.min(joinableServers - requiredJoinable, joinableServers - requiredTotal);
serversToRestart = Math.min(serversToRestart, (availableSlots - 1500) / 80);
if (serversToRestart <= 5)
serversToRestart = 0;
}
}
else if (serverGroup.getName().equalsIgnoreCase("Halloween"))
{
@ -382,6 +422,12 @@ public class ServerMonitor
while (serversToAdd > 0)
{
serverNum = serverGroup.generateUniqueId(serverNum + 1);
while (_deadServers.contains(serverGroup.getPrefix() + "-" + serverNum))
{
serverNum = serverGroup.generateUniqueId(serverNum + 1);
}
Collections.sort(_dedicatedServers, new DedicatedServerSorter());
DedicatedServer bestServer = getBestDedicatedServer(_dedicatedServers, serverGroup);
@ -401,6 +447,18 @@ public class ServerMonitor
serversToAdd--;
}
List<MinecraftServer> servers = new ArrayList<MinecraftServer>();
servers.addAll(serverGroup.getServers());
Collections.sort(servers, new ServerSorter());
while (serversToRestart > 0)
{
MinecraftServer server = servers.get(servers.size() - serversToRestart);
new SuicideCommand(server.getName(), _region).publish();
log("[RESTART/KILL EXCESS] : " + server.getName() + ":" + server.getPublicAddress() + " " + server.getPlayerCount() + " players");
serversToRestart--;
}
}
private static void killServer(final String serverName, final String serverAddress, final int players, final String message, final boolean announce)

View File

@ -0,0 +1,19 @@
package mineplex.servermonitor;
import java.util.Comparator;
import mineplex.serverdata.MinecraftServer;
public class ServerSorter implements Comparator<MinecraftServer>
{
@Override
public int compare(MinecraftServer first, MinecraftServer second)
{
if (Integer.parseInt(first.getName().split("-")[1]) < Integer.parseInt(second.getName().split("-")[1]))
return -1;
else if (Integer.parseInt(second.getName().split("-")[1]) < Integer.parseInt(first.getName().split("-")[1]))
return 1;
return 0;
}
}