Update + added benchmark tool

This commit is contained in:
Felix Cravic 2020-04-13 17:17:21 +02:00
parent 126d778221
commit c2580789b9
16 changed files with 262 additions and 43 deletions

View File

@ -13,6 +13,8 @@ import fr.themode.minestom.item.Material;
import fr.themode.minestom.net.packet.server.play.DeclareRecipesPacket;
import fr.themode.minestom.recipe.RecipeManager;
import fr.themode.minestom.recipe.ShapelessRecipe;
import fr.themode.minestom.utils.time.TimeUnit;
import fr.themode.minestom.utils.time.UpdateOption;
public class Main {
@ -38,6 +40,7 @@ public class Main {
shapelessRecipe.addIngredient(ingredient);
recipeManager.addRecipe(shapelessRecipe);
MinecraftServer.getBenchmarkManager().enable(new UpdateOption(2500, TimeUnit.MILLISECOND));
PlayerInit.init();

View File

@ -1,7 +1,10 @@
package fr.themode.demo;
import fr.themode.demo.entity.ChickenCreature;
import fr.themode.demo.generator.ChunkGeneratorDemo;
import fr.themode.minestom.MinecraftServer;
import fr.themode.minestom.benchmark.BenchmarkManager;
import fr.themode.minestom.benchmark.ThreadResult;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.entity.GameMode;
@ -12,8 +15,14 @@ import fr.themode.minestom.inventory.Inventory;
import fr.themode.minestom.inventory.InventoryType;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.net.ConnectionManager;
import fr.themode.minestom.timer.TaskRunnable;
import fr.themode.minestom.utils.MathUtils;
import fr.themode.minestom.utils.Position;
import fr.themode.minestom.utils.Vector;
import fr.themode.minestom.utils.time.TimeUnit;
import fr.themode.minestom.utils.time.UpdateOption;
import java.util.Map;
public class PlayerInit {
@ -37,6 +46,33 @@ public class PlayerInit {
public static void init() {
ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager();
MinecraftServer.getSchedulerManager().addRepeatingTask(new TaskRunnable() {
@Override
public void run() {
long ramUsage = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
ramUsage /= 1e6; // To MB
String benchmarkMessage = "";
for (Map.Entry<String, ThreadResult> resultEntry : benchmarkManager.getResultMap().entrySet()) {
String name = resultEntry.getKey();
ThreadResult result = resultEntry.getValue();
benchmarkMessage += name;
benchmarkMessage += ": ";
benchmarkMessage += MathUtils.round(result.getCpuPercentage(), 2) + "% CPU ";
benchmarkMessage += MathUtils.round(result.getUserPercentage(), 2) + "% USER ";
benchmarkMessage += MathUtils.round(result.getBlockedPercentage(), 2) + "% BLOCKED ";
benchmarkMessage += "\n";
}
if (benchmarkMessage.length() > 0)
System.out.println(benchmarkMessage);
for (Player player : connectionManager.getOnlinePlayers()) {
player.sendHeaderFooter("RAM USAGE: " + ramUsage + " MB", "", '&');
}
}
}, new UpdateOption(5, TimeUnit.TICK));
connectionManager.setResponseDataConsumer(responseData -> {
responseData.setName("1.15.2");
@ -78,8 +114,8 @@ public class PlayerInit {
p.teleport(player.getPosition());
}
//ChickenCreature chickenCreature = new ChickenCreature(player.getPosition());
//chickenCreature.setInstance(player.getInstance());
ChickenCreature chickenCreature = new ChickenCreature(player.getPosition());
chickenCreature.setInstance(player.getInstance());
});

View File

@ -1,6 +1,7 @@
package fr.themode.minestom;
import com.github.simplenet.Server;
import fr.themode.minestom.benchmark.BenchmarkManager;
import fr.themode.minestom.command.CommandManager;
import fr.themode.minestom.data.DataManager;
import fr.themode.minestom.entity.EntityManager;
@ -23,15 +24,33 @@ import fr.themode.minestom.utils.Utils;
public class MinecraftServer {
// Thread pools
// Threads
public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark";
public static final String THREAD_NAME_PACKET_WRITER = "Ms-PacketWriterPool";
public static final int THREAD_COUNT_PACKET_WRITER = 2;
public static final String THREAD_NAME_IO = "Ms-IOPool";
public static final int THREAD_COUNT_IO = 2;
public static final String THREAD_NAME_BLOCK_BATCH = "Ms-BlockBatchPool";
public static final int THREAD_COUNT_BLOCK_BATCH = 2;
public static final String THREAD_NAME_BLOCK_UPDATE = "Ms-BlockUpdatePool";
public static final int THREAD_COUNT_BLOCK_UPDATE = 2;
public static final String THREAD_NAME_ENTITIES = "Ms-EntitiesPool";
public static final int THREAD_COUNT_ENTITIES = 2;
public static final String THREAD_NAME_ENTITIES_PATHFINDING = "Ms-EntitiesPathFinding";
public static final int THREAD_COUNT_ENTITIES_PATHFINDING = 2;
public static final String THREAD_NAME_PLAYERS_ENTITIES = "Ms-PlayersPool";
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2;
public static final int THREAD_COUNT_SCHEDULER = 2;
public static final String THREAD_NAME_SCHEDULER = "Ms-SchedulerPool";
public static final int THREAD_COUNT_SCHEDULER = 1;
// Config
public static final int CHUNK_VIEW_DISTANCE = 5;
public static final int ENTITY_VIEW_DISTANCE = 2;
@ -55,6 +74,7 @@ public class MinecraftServer {
private static DataManager dataManager;
private static TeamManager teamManager;
private static SchedulerManager schedulerManager;
private static BenchmarkManager benchmarkManager;
private static MinecraftServer minecraftServer;
@ -71,6 +91,7 @@ public class MinecraftServer {
dataManager = new DataManager();
teamManager = new TeamManager();
schedulerManager = new SchedulerManager();
benchmarkManager = new BenchmarkManager();
server = new Server();
@ -125,6 +146,10 @@ public class MinecraftServer {
return schedulerManager;
}
public static BenchmarkManager getBenchmarkManager() {
return benchmarkManager;
}
public static ConnectionManager getConnectionManager() {
return connectionManager;
}

View File

@ -0,0 +1,122 @@
package fr.themode.minestom.benchmark;
import fr.themode.minestom.MinecraftServer;
import fr.themode.minestom.utils.time.UpdateOption;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.*;
import static fr.themode.minestom.MinecraftServer.*;
import static org.junit.Assert.assertTrue;
public class BenchmarkManager {
public static ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
private static List<String> threads = Arrays.asList(THREAD_NAME_PACKET_WRITER, THREAD_NAME_IO,
THREAD_NAME_BLOCK_BATCH, THREAD_NAME_BLOCK_UPDATE, THREAD_NAME_ENTITIES, THREAD_NAME_ENTITIES_PATHFINDING,
THREAD_NAME_PLAYERS_ENTITIES, THREAD_NAME_SCHEDULER);
static {
assertTrue(threadMXBean.isThreadCpuTimeSupported());
assertTrue(threadMXBean.isCurrentThreadCpuTimeSupported());
threadMXBean.setThreadContentionMonitoringEnabled(true);
threadMXBean.setThreadCpuTimeEnabled(true);
assertTrue(threadMXBean.isThreadCpuTimeEnabled());
}
private Map<Long, Long> lastCpuTimeMap = new HashMap<>();
private Map<Long, Long> lastUserTimeMap = new HashMap<>();
private Map<Long, Long> lastBlockedMap = new HashMap<>();
private Map<String, ThreadResult> resultMap = new HashMap<>();
private boolean enabled = false;
private volatile boolean stop = false;
private UpdateOption updateOption;
private Thread thread;
private long time;
public void enable(UpdateOption updateOption) {
if (enabled)
throw new IllegalStateException("A benchmark is already running, please disable it first.");
this.updateOption = updateOption;
time = updateOption.getTimeUnit().toMilliseconds(updateOption.getValue());
this.thread = new Thread(null, () -> {
while (!stop) {
refreshData();
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
stop = false;
}, MinecraftServer.THREAD_NAME_BENCHMARK, 0L);
this.thread.start();
this.enabled = true;
}
public void disable() {
this.stop = true;
this.enabled = false;
}
public Map<String, ThreadResult> getResultMap() {
return Collections.unmodifiableMap(resultMap);
}
private void refreshData() {
ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds());
for (ThreadInfo threadInfo2 : threadInfo) {
String name = threadInfo2.getThreadName();
boolean shouldBenchmark = false;
for (String thread : threads) {
if (name.startsWith(thread)) {
shouldBenchmark = true;
break;
}
}
if (!shouldBenchmark)
continue;
long id = threadInfo2.getThreadId();
long lastCpuTime = lastCpuTimeMap.getOrDefault(id, 0L);
long lastUserTime = lastUserTimeMap.getOrDefault(id, 0L);
long lastBlockedTime = lastBlockedMap.getOrDefault(id, 0L);
long blockedTime = threadInfo2.getBlockedTime();
//long waitedTime = threadInfo2.getWaitedTime();
long cpuTime = threadMXBean.getThreadCpuTime(id);
long userTime = threadMXBean.getThreadUserTime(id);
lastCpuTimeMap.put(id, cpuTime);
lastUserTimeMap.put(id, userTime);
lastBlockedMap.put(id, blockedTime);
double totalCpuTime = (double) (cpuTime - lastCpuTime) / 1000000D;
double totalUserTime = (double) (userTime - lastUserTime) / 1000000D;
long totalBlocked = blockedTime - lastBlockedTime;
double cpuPercentage = totalCpuTime / (double) time * 100L;
double userPercentage = totalUserTime / (double) time * 100L;
double blockedPercentage = totalBlocked / (double) time * 100L;
ThreadResult threadResult = new ThreadResult(cpuPercentage, userPercentage, blockedPercentage);
resultMap.put(name, threadResult);
}
}
}

View File

@ -0,0 +1,24 @@
package fr.themode.minestom.benchmark;
public class ThreadResult {
private double cpuPercentage, userPercentage, blockedPercentage;
protected ThreadResult(double cpuPercentage, double userPercentage, double blockedPercentage) {
this.cpuPercentage = cpuPercentage;
this.userPercentage = userPercentage;
this.blockedPercentage = blockedPercentage;
}
public double getCpuPercentage() {
return cpuPercentage;
}
public double getUserPercentage() {
return userPercentage;
}
public double getBlockedPercentage() {
return blockedPercentage;
}
}

View File

@ -18,8 +18,8 @@ public class EntityManager {
private UpdateType updateType = UpdateType.PER_INSTANCE;
private Set<Instance> instances = instanceManager.getInstances();
private ExecutorService entitiesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_ENTITIES, "Ms-EntitiesPool");
private ExecutorService playersPool = new MinestomThread(MinecraftServer.THREAD_COUNT_PLAYERS_ENTITIES, "Ms-PlayersPool");
private ExecutorService entitiesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_ENTITIES, MinecraftServer.THREAD_NAME_ENTITIES);
private ExecutorService playersPool = new MinestomThread(MinecraftServer.THREAD_COUNT_PLAYERS_ENTITIES, MinecraftServer.THREAD_NAME_PLAYERS_ENTITIES);
private ConcurrentLinkedQueue<Player> waitingPlayers = new ConcurrentLinkedQueue<>();
@ -187,6 +187,6 @@ public class EntityManager {
PER_CHUNK,
PER_ENTITY_TYPE,
PER_INSTANCE,
SINGLE_THREADED;
SINGLE_THREADED
}
}

View File

@ -12,7 +12,7 @@ import java.util.function.Consumer;
public class EntityPathFinder {
private ExecutorService pathfindingPool = new MinestomThread(MinecraftServer.THREAD_COUNT_ENTITIES_PATHFINDING, "Ms-EntitiesPathFinding");
private ExecutorService pathfindingPool = new MinestomThread(MinecraftServer.THREAD_COUNT_ENTITIES_PATHFINDING, MinecraftServer.THREAD_NAME_ENTITIES_PATHFINDING);
private Entity entity;

View File

@ -59,9 +59,8 @@ public class InstanceContainer extends Instance {
chunk.UNSAFE_setBlock(index, blockId, data);
executeNeighboursBlockPlacementRule(blockId, blockPosition);
executeNeighboursBlockPlacementRule(blockPosition);
// TODO instead of sending a block change packet each time, cache changed blocks and flush them every tick with a MultiBlockChangePacket
sendBlockChange(chunk, x, y, z, blockId);
}
}
@ -83,11 +82,10 @@ public class InstanceContainer extends Instance {
chunk.UNSAFE_setCustomBlock(index, blockId, data);
executeNeighboursBlockPlacementRule(blockId, blockPosition);
executeNeighboursBlockPlacementRule(blockPosition);
callBlockPlace(chunk, index, x, y, z);
// TODO instead of sending a block change packet each time, cache changed blocks and flush them every tick with a MultiBlockChangePacket
short id = BLOCK_MANAGER.getBlock(blockId).getType();
sendBlockChange(chunk, x, y, z, id);
}
@ -132,7 +130,7 @@ public class InstanceContainer extends Instance {
return blockId;
}
private void executeNeighboursBlockPlacementRule(short blockId, BlockPosition blockPosition) {
private void executeNeighboursBlockPlacementRule(BlockPosition blockPosition) {
for (int offsetX = -1; offsetX < 2; offsetX++) {
for (int offsetY = -1; offsetY < 2; offsetY++) {
for (int offsetZ = -1; offsetZ < 2; offsetZ++) {

View File

@ -12,7 +12,7 @@ import java.util.concurrent.ExecutorService;
public class InstanceManager {
private ExecutorService blocksPool = new MinestomThread(MinecraftServer.THREAD_COUNT_BLOCK_UPDATE, "Ms-BlockUpdatePool");
private ExecutorService blocksPool = new MinestomThread(MinecraftServer.THREAD_COUNT_BLOCK_UPDATE, MinecraftServer.THREAD_NAME_BLOCK_UPDATE);
private Set<Instance> instances = Collections.synchronizedSet(new HashSet<>());

View File

@ -8,6 +8,6 @@ import java.util.concurrent.ExecutorService;
public interface InstanceBatch extends BlockModifier {
ExecutorService batchesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_BLOCK_BATCH, "Ms-BlockBatchPool");
ExecutorService batchesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_BLOCK_BATCH, MinecraftServer.THREAD_NAME_BLOCK_BATCH);
}

View File

@ -56,6 +56,8 @@ public class RedstonePlacementRule extends BlockPlacementRule {
north = "side";
}
// TODO power
return Block.REDSTONE_WIRE.withProperties(east, north, power, south, west);
}

View File

@ -97,7 +97,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View
public void update() {
WindowItemsPacket windowItemsPacket = getWindowItemsPacket();
getViewers().forEach(p -> p.getPlayerConnection().sendPacket(windowItemsPacket));
sendPacketToViewers(windowItemsPacket);
}
@Override
@ -129,7 +129,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View
setSlotPacket.windowId = 1;
setSlotPacket.slot = (short) slot;
setSlotPacket.itemStack = itemStack;
getViewers().forEach(player -> player.getPlayerConnection().sendPacket(setSlotPacket));
sendPacketToViewers(setSlotPacket);
}
}

View File

@ -7,7 +7,7 @@ import java.util.concurrent.ExecutorService;
public class IOManager {
private static final ExecutorService IO_POOL = new MinestomThread(MinecraftServer.THREAD_COUNT_IO, "Ms-IOPool");
private static final ExecutorService IO_POOL = new MinestomThread(MinecraftServer.THREAD_COUNT_IO, MinecraftServer.THREAD_NAME_IO);
public static void submit(Runnable runnable) {
IO_POOL.execute(runnable);

View File

@ -14,7 +14,7 @@ import java.util.function.Consumer;
public class PacketWriterUtils {
private static ExecutorService batchesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_PACKET_WRITER, "Ms-PacketWriterPool");
private static ExecutorService batchesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_PACKET_WRITER, MinecraftServer.THREAD_NAME_PACKET_WRITER);
public static void writeCallbackPacket(ServerPacket serverPacket, Consumer<Packet> consumer) {
batchesPool.execute(() -> {

View File

@ -5,7 +5,6 @@ import fr.themode.minestom.utils.thread.MinestomThread;
import fr.themode.minestom.utils.time.CooldownUtils;
import fr.themode.minestom.utils.time.UpdateOption;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
@ -14,7 +13,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class SchedulerManager {
private static final AtomicInteger COUNTER = new AtomicInteger();
private static ExecutorService batchesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_SCHEDULER, "Ms-SchedulerPool");
private static ExecutorService batchesPool = new MinestomThread(MinecraftServer.THREAD_COUNT_SCHEDULER, MinecraftServer.THREAD_NAME_SCHEDULER);
private List<Task> tasks = new CopyOnWriteArrayList<>();
public int addTask(TaskRunnable runnable, UpdateOption updateOption, int maxCallCount) {
@ -36,36 +35,28 @@ public class SchedulerManager {
}
public void removeTask(int taskId) {
synchronized (tasks) {
this.tasks.removeIf(task -> task.getId() == taskId);
}
this.tasks.removeIf(task -> task.getId() == taskId);
}
public void update() {
long time = System.currentTimeMillis();
batchesPool.execute(() -> {
for (Task task : tasks) {
UpdateOption updateOption = task.getUpdateOption();
long lastUpdate = task.getLastUpdateTime();
boolean hasCooldown = CooldownUtils.hasCooldown(time, lastUpdate, updateOption.getTimeUnit(), updateOption.getValue());
if (!hasCooldown) {
TaskRunnable runnable = task.getRunnable();
int maxCallCount = task.getMaxCallCount();
int callCount = runnable.getCallCount() + 1;
runnable.setCallCount(callCount);
synchronized (tasks) {
Iterator<Task> iterator = tasks.iterator();
while (iterator.hasNext()) {
Task task = iterator.next();
runnable.run();
UpdateOption updateOption = task.getUpdateOption();
long lastUpdate = task.getLastUpdateTime();
boolean hasCooldown = CooldownUtils.hasCooldown(time, lastUpdate, updateOption.getTimeUnit(), updateOption.getValue());
if (!hasCooldown) {
TaskRunnable runnable = task.getRunnable();
int maxCallCount = task.getMaxCallCount();
int callCount = runnable.getCallCount() + 1;
runnable.setCallCount(callCount);
task.refreshLastUpdateTime(time);
runnable.run();
task.refreshLastUpdateTime(time);
if (callCount == maxCallCount) {
iterator.remove();
}
if (callCount == maxCallCount) {
tasks.remove(task);
}
}
}

View File

@ -10,4 +10,22 @@ public class MathUtils {
return num * num;
}
public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
return (double) tmp / factor;
}
public static float round(float value, int places) {
if (places < 0) throw new IllegalArgumentException();
long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
return (float) tmp / factor;
}
}