diff --git a/src/main/java/net/minecraft/world/World.java b/src/main/java/net/minecraft/world/World.java index d5e3af6..bab96ef 100644 --- a/src/main/java/net/minecraft/world/World.java +++ b/src/main/java/net/minecraft/world/World.java @@ -20,6 +20,8 @@ import org.ultramine.server.ServerLoadBalancer; import org.ultramine.server.chunk.CallbackAddDependency; import org.ultramine.server.chunk.ChunkHash; +import org.ultramine.server.chunk.ChunkProfiler; +import org.ultramine.server.chunk.ChunkProfiler.WorldChunkProfiler; import org.ultramine.server.chunk.IChunkLoadCallback; import net.minecraft.block.Block; @@ -190,6 +192,7 @@ this.worldInfo = new WorldInfo(p_i45368_4_, p_i45368_2_); this.provider = p_i45368_3_; perWorldStorage = new MapStorage((ISaveHandler)null); + chunkProfiler = ChunkProfiler.instance().getForWorld(provider.dimensionId); } // Broken up so that the WorldClient gets the chance to set the mapstorage object before the dimension initializes @@ -304,6 +307,8 @@ this.calculateInitialSkylight(); this.calculateInitialWeather(); + + chunkProfiler = ChunkProfiler.instance().getForWorld(provider.dimensionId); } private static MapStorage s_mapStorage; @@ -1962,8 +1967,10 @@ { TileEntity tileentity = (TileEntity)iterator.next(); - if (!tileentity.isInvalid() && tileentity.hasWorldObj() && activeChunkSet.containsKey(ChunkHash.chunkToKey(tileentity.xCoord >> 4, tileentity.zCoord >> 4))) + int key = ChunkHash.chunkToKey(tileentity.xCoord >> 4, tileentity.zCoord >> 4); + if (!tileentity.isInvalid() && tileentity.hasWorldObj() && activeChunkSet.containsKey(key)) { + chunkProfiler.startChunk(key); try { tileentity.updateEntity(); @@ -1984,6 +1991,7 @@ throw new ReportedException(crashreport); } } + chunkProfiler.endChunk(); } if (tileentity.isInvalid()) @@ -2082,6 +2090,7 @@ if (canUpdate) { + chunkProfiler.startChunk(p_72866_1_); p_72866_1_.lastTickPosX = p_72866_1_.posX; p_72866_1_.lastTickPosY = p_72866_1_.posY; p_72866_1_.lastTickPosZ = p_72866_1_.posZ; @@ -2170,6 +2179,8 @@ p_72866_1_.riddenByEntity = null; } } + + chunkProfiler.endChunk(); } } @@ -4096,6 +4107,7 @@ public static final int MAX_BLOCK_COORD = 500000;//524288; private final ServerLoadBalancer balancer = new ServerLoadBalancer(this); + private final WorldChunkProfiler chunkProfiler; public Chunk getChunkIfExists(int cx, int cz) { diff --git a/src/main/java/org/ultramine/commands/basic/TechCommands.java b/src/main/java/org/ultramine/commands/basic/TechCommands.java index 1a19c21..917ee08 100644 --- a/src/main/java/org/ultramine/commands/basic/TechCommands.java +++ b/src/main/java/org/ultramine/commands/basic/TechCommands.java @@ -1,5 +1,7 @@ package org.ultramine.commands.basic; +import java.util.Arrays; + import net.minecraft.entity.Entity; import net.minecraft.entity.EnumCreatureType; import net.minecraft.entity.item.EntityItem; @@ -24,6 +26,7 @@ import org.ultramine.server.Restarter; import org.ultramine.server.Teleporter; import org.ultramine.server.WorldsConfig.WorldConfig.Border; +import org.ultramine.server.chunk.ChunkProfiler; import org.ultramine.server.chunk.IChunkLoadCallback; import cpw.mods.fml.common.FMLCommonHandler; @@ -535,4 +538,50 @@ } } } + + @Command( + name = "chunkdebug", + group = "technical", + permissions = {"command.chunkdebug"}, + syntax = { + "", + "[start stop]", + "[top]", + "[top] ", + "[top] <%count>" + } + ) + public static void chunkdebug(CommandContext ctx) + { + if(ctx.getAction().equals("start")) + { + ChunkProfiler.instance().setEnabled(true); + ctx.sendMessage("command.chunkdebug.start"); + } + else if(ctx.getAction().equals("stop")) + { + ChunkProfiler.instance().setEnabled(false); + ctx.sendMessage("command.chunkdebug.stop"); + } + else + { + if(!ChunkProfiler.instance().isEnabled()) + ctx.failure("command.chunkdebug.notstart"); + String act2 = ctx.contains("list") ? ctx.get("list").asString() : "average"; + int count = ctx.contains("count") ? ctx.get("count").asInt(1) : 9; + ChunkProfiler.ChunkData[] results; + if(act2.startsWith("a")) + results = ChunkProfiler.instance().getAverageTop(); + else// if(act2.startsWith("p")) + results = ChunkProfiler.instance().getPeakTop(); + + ctx.sendMessage("command.chunkdebug.top.head"); + for(int i = 0; i < Math.min(count, results.length); i++) + { + ChunkProfiler.ChunkData chunk = results[i]; + ctx.sendMessage(GOLD, " - [%s](%s, %s) -> %s%% (%s%%)", + chunk.getDimension(), chunk.getChunkX() << 4, chunk.getChunkZ() << 4, (chunk.getAverage()/5000)/100d, (chunk.getPeak()/5000)/100d); + } + } + } } diff --git a/src/main/java/org/ultramine/server/UMEventHandler.java b/src/main/java/org/ultramine/server/UMEventHandler.java index 5628b25..b17433e 100644 --- a/src/main/java/org/ultramine/server/UMEventHandler.java +++ b/src/main/java/org/ultramine/server/UMEventHandler.java @@ -1,6 +1,7 @@ package org.ultramine.server; import org.ultramine.server.UltramineServerConfig.SettingsConf.MessagesConf.AutoBroacastConf; +import org.ultramine.server.chunk.ChunkProfiler; import org.ultramine.server.util.BasicTypeParser; import org.ultramine.server.util.WarpLocation; @@ -48,10 +49,12 @@ { if(e.phase == TickEvent.Phase.START) { + MinecraftServer server = MinecraftServer.getServer(); + Teleporter.tick(); + ChunkProfiler.instance().tick(server.getTickCounter()); AutoBroacastConf cfg = ConfigurationHandler.getServerConfig().settings.messages.autobroadcast; - MinecraftServer server = MinecraftServer.getServer(); if(cfg.enabled && server.getTickCounter() % (cfg.intervalSeconds*20) == 0) { if(cfg.showDebugInfo) diff --git a/src/main/java/org/ultramine/server/UltramineServerModContainer.java b/src/main/java/org/ultramine/server/UltramineServerModContainer.java index 7c9032e..618f69c 100644 --- a/src/main/java/org/ultramine/server/UltramineServerModContainer.java +++ b/src/main/java/org/ultramine/server/UltramineServerModContainer.java @@ -36,6 +36,7 @@ import org.ultramine.commands.syntax.DefaultCompleters; import org.ultramine.permission.commands.BasicPermissionCommands; import org.ultramine.permission.internal.OpPermissionProxySet; +import org.ultramine.server.chunk.ChunkProfiler; import org.ultramine.server.data.Databases; import org.ultramine.server.data.ServerDataLoader; import org.ultramine.server.data.player.PlayerCoreData; @@ -123,6 +124,7 @@ public void serverStopped(FMLServerStoppedEvent e) { MinecraftServer.getServer().getMultiWorld().unregister(); + ChunkProfiler.instance().setEnabled(false); } @NetworkCheckHandler diff --git a/src/main/java/org/ultramine/server/chunk/ChunkHash.java b/src/main/java/org/ultramine/server/chunk/ChunkHash.java index 3705c60..6873d60 100644 --- a/src/main/java/org/ultramine/server/chunk/ChunkHash.java +++ b/src/main/java/org/ultramine/server/chunk/ChunkHash.java @@ -20,4 +20,9 @@ { return (short)(((x&15)<<12) | ((z&15)<<8) | (y&255)); } + + public static long worldChunkToKey(int dim, int x, int z) + { + return dim << 32 | (x & 0xffff) << 16 | (z & 0xffff); + } } diff --git a/src/main/java/org/ultramine/server/chunk/ChunkProfiler.java b/src/main/java/org/ultramine/server/chunk/ChunkProfiler.java new file mode 100644 index 0000000..f977edf --- /dev/null +++ b/src/main/java/org/ultramine/server/chunk/ChunkProfiler.java @@ -0,0 +1,204 @@ +package org.ultramine.server.chunk; + +import java.util.Arrays; +import java.util.Comparator; + +import org.apache.commons.lang3.ArrayUtils; + +import gnu.trove.map.TLongObjectMap; +import gnu.trove.map.hash.TLongObjectHashMap; +import gnu.trove.procedure.TObjectProcedure; +import net.minecraft.entity.Entity; +import net.minecraft.util.MathHelper; + +public class ChunkProfiler +{ + private static final ChunkProfiler INSTANCE = new ChunkProfiler(); + + private boolean isChunkDebugEnabled = false; + private TLongObjectMap chunkTimeMap = new TLongObjectHashMap(512); + + public static ChunkProfiler instance() + { + return INSTANCE; + } + + private void endChunk(long key, long startTime) + { + if(isChunkDebugEnabled) + { + ChunkData data = chunkTimeMap.get(key); + if(data == null) + { + data = new ChunkData(key); + chunkTimeMap.put(key, data); + } + + data.current += (System.nanoTime() - startTime); + } + } + + public void tick(int tick) + { + if(chunkTimeMap.size() != 0) + { + if(tick % 600 == 0) + chunkTimeMap.clear(); + else + chunkTimeMap.forEachValue(updateFunc); + } + } + + public void setEnabled(boolean enabled) + { + this.isChunkDebugEnabled = enabled; + chunkTimeMap.clear(); + } + + public boolean isEnabled() + { + return isChunkDebugEnabled; + } + + public ChunkData[] getAverageTop() + { + ChunkData[] arr = chunkTimeMap.values(new ChunkData[chunkTimeMap.size()]); + Arrays.sort(arr, averageComparator); + ArrayUtils.reverse(arr); + return arr; + } + + public ChunkData[] getPeakTop() + { + ChunkData[] arr = chunkTimeMap.values(new ChunkData[chunkTimeMap.size()]); + Arrays.sort(arr, peakComparator); + ArrayUtils.reverse(arr); + return arr; + } + + public WorldChunkProfiler getForWorld(int dim) + { + return new WorldChunkProfiler(dim); + } + + public static class ChunkData + { + private final long key; + private long current; + private long average; + private long peak; + + ChunkData(long key) + { + this.key = key; + } + + public int getDimension() + { + return (int)(key >>> 32); + } + + public int getChunkX() + { + return ChunkHash.keyToX((int)(key & 0xFFFFFFFF)); + } + + public int getChunkZ() + { + return ChunkHash.keyToZ((int)(key & 0xFFFFFFFF)); + } + + public long getCurrent() + { + return current; + } + + public long getAverage() + { + return average; + } + + public long getPeak() + { + return peak; + } + + public String toString() + { + return "["+getDimension()+"]("+getChunkX()+", "+getChunkZ()+") -> all:"+current+" average:"+average+" peak:"+peak+";"; + } + } + + private static final TObjectProcedure updateFunc = new TObjectProcedure() + { + @Override + public boolean execute(ChunkData data) + { + data.average = (long)(data.average*0.95 + data.current*0.05); + if(data.current > data.peak) + data.peak = data.current; + data.current = 0; + return true; + } + }; + + private static final Comparator averageComparator = new Comparator() + { + @Override + public int compare(ChunkData c1, ChunkData c2) + { + return Long.compare(c1.average, c2.average); + } + }; + + private static final Comparator peakComparator = new Comparator() + { + @Override + public int compare(ChunkData c1, ChunkData c2) + { + return Long.compare(c1.peak, c2.peak); + } + }; + + public class WorldChunkProfiler + { + private final int dim; + + private long curChunk; + private long curChunkStart; + + private WorldChunkProfiler(int dim) + { + this.dim = dim; + } + + public void startChunk(Entity entity) + { + if(isChunkDebugEnabled) + startChunk(MathHelper.floor_double(entity.posX) >> 4, MathHelper.floor_double(entity.posZ) >> 4); + } + + public void startChunk(int cx, int cz) + { + if(isChunkDebugEnabled) + { + curChunk = ChunkHash.worldChunkToKey(dim, cx, cz); + curChunkStart = System.nanoTime(); + } + } + + public void startChunk(int key) + { + if(isChunkDebugEnabled) + { + curChunk = dim << 32 | key; + curChunkStart = System.nanoTime(); + } + } + + public void endChunk() + { + ChunkProfiler.this.endChunk(curChunk, curChunkStart); + } + } +} diff --git a/src/main/resources/assets/ultramine/lang/en_US.lang b/src/main/resources/assets/ultramine/lang/en_US.lang index 797237f..a2939d3 100644 --- a/src/main/resources/assets/ultramine/lang/en_US.lang +++ b/src/main/resources/assets/ultramine/lang/en_US.lang @@ -203,3 +203,10 @@ command.genworld.stop=World generation stopped command.genworld.complete=World generation completed (generated %s chunks) command.genworld.process=World generation: generated %s chunks + +command.chunkdebug.usage=/chunkdebug OR /chunkdebug [average|peak] [count] +command.chunkdebug.description=Starts per chunk profiling or displays results +command.chunkdebug.start=Chunk profiling started +command.chunkdebug.stop=Chunk profiling stopped +command.chunkdebug.notstart=Chunk profiling has not started yet. Use /chunkdebug start +command.chunkdebug.top.head=Chunk top: diff --git a/src/main/resources/assets/ultramine/lang/ru_RU.lang b/src/main/resources/assets/ultramine/lang/ru_RU.lang index b390fc8..5f118d3 100644 --- a/src/main/resources/assets/ultramine/lang/ru_RU.lang +++ b/src/main/resources/assets/ultramine/lang/ru_RU.lang @@ -203,3 +203,10 @@ command.genworld.stop=Генерация мира остановлена command.genworld.complete=Генерация мира завершена (сгенерировано %s чанков) command.genworld.process=Генерация мира: сгенерировано %s чанков + +command.chunkdebug.usage=/chunkdebug ИЛИ /chunkdebug [average|peak] [количество] +command.chunkdebug.description=Запускает почанковое профилирование или выводит результаты +command.chunkdebug.start=Почанковое профилирование запущено +command.chunkdebug.stop=Почанковое профилирование остановлено +command.chunkdebug.notstart=Почанковое профилирование еще не запущено. Используйте /chunkdebug start +command.chunkdebug.top.head=Топ чанков: