Skip to content
This repository has been archived by the owner on Mar 8, 2022. It is now read-only.

Commit

Permalink
add chunk profiler
Browse files Browse the repository at this point in the history
  • Loading branch information
Librazy committed Jan 2, 2019
1 parent 63a7e6e commit 870364a
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ targetCompatibility = 1.8

repositories {
jcenter()
// mavenLocal()
maven {
name 'Spigot'
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/cat/nyaa/yasui/ChunkCoordinate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cat.nyaa.yasui;

import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;

class ChunkCoordinate {

private final int x;

private final int z;

private ChunkCoordinate(int x, int z) {
this.x = x;
this.z = z;
}

public static ChunkCoordinate of(int x, int z) {
return new ChunkCoordinate(x, z);
}

public static ChunkCoordinate of(Block b) {
return new ChunkCoordinate(b.getX() >> 4, b.getZ() >> 4);
}

public static ChunkCoordinate of(Entity b) {
return new ChunkCoordinate(b.getLocation().getBlockX() >> 4, b.getLocation().getBlockZ() >> 4);
}

public ChunkCoordinate add(ChunkCoordinate another) {
return new ChunkCoordinate(x + another.x, z + another.z);
}

@Override
public int hashCode() {
return (x << 16 + x >> 16) ^ z;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof ChunkCoordinate) {
ChunkCoordinate pair = (ChunkCoordinate) o;
return (pair.x == x) && (pair.z == z);
}
return false;
}

@Override
public String toString() {
return "Chunk " + x + ", " + z;
}

public BaseComponent getComponent() {
TextComponent component = new TextComponent(I18n.format("user.chunk.coord", x, z));
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new BaseComponent[]{new TextComponent(I18n.format("user.chunk.hover", x << 4, (x << 4) + 16, z << 4, (z << 4) + 16))}));
component.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, I18n.format("user.chunk.tp", (x << 4) + 8, (z << 4) + 8)));
return component;
}

public int getX() {
return x;
}

public int getZ() {
return z;
}
}
86 changes: 86 additions & 0 deletions src/main/java/cat/nyaa/yasui/CommandHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@

import cat.nyaa.nyaacore.CommandReceiver;
import cat.nyaa.nyaacore.LanguageRepository;
import cat.nyaa.nyaacore.Message;
import cat.nyaa.nyaacore.Pair;
import cat.nyaa.nyaacore.utils.NmsUtils;
import com.google.common.collect.EnumMultiset;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.bukkit.Bukkit;
import org.bukkit.GameRule;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;

import java.util.*;

public class CommandHandler extends CommandReceiver {
private final Yasui plugin;
Expand Down Expand Up @@ -52,4 +64,78 @@ public void commandDebug(CommandSender sender, Arguments args) {
public void commandReload(CommandSender sender, Arguments args) {
plugin.reload();
}

@SubCommand(value = "chunkevents", permission = "yasui.profiler")
public void commandChunkEvents(CommandSender sender, Arguments args) {
World world = getWorld(sender, args);
if (plugin.profilerStatsMonitor == null) {
msg(sender, "user.profiler.not_enabled");

}
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Deque<Pair<Long, Map<ChunkCoordinate, ProfilerStatsMonitor.ChunkStat>>> redstoneStats = plugin.profilerStatsMonitor.getRedstoneStats(world);
Iterator<Pair<Long, Map<ChunkCoordinate, ProfilerStatsMonitor.ChunkStat>>> descendingIterator = redstoneStats.descendingIterator();
List<Pair<Long, Map<ChunkCoordinate, ProfilerStatsMonitor.ChunkStat>>> snapshot = new ArrayList<>(600);
while (descendingIterator.hasNext()) {
snapshot.add(descendingIterator.next());
if (snapshot.size() == 600) break;
}
Map<ChunkCoordinate, ProfilerStatsMonitor.ChunkStat> stat = snapshot.stream().map(Pair::getValue).reduce(new HashMap<>(), (total, tick) -> {
tick.forEach((chunk, value) -> {
if (value.getPhysics() != 0 || value.getRedstone() != 0) {
total.computeIfAbsent(chunk, (ignored) -> new ProfilerStatsMonitor.ChunkStat()).add(value);
}
});
return total;
});
stat.entrySet().stream().sorted(Comparator.comparing(e -> -e.getValue().getRedstone())).limit(plugin.config.profiler_event_chunk_count).forEach(
e -> new Message("").append(e.getKey().getComponent()).append(I18n.format("user.chunk.total", e.getValue().getPhysics() + e.getValue().getRedstone())).append(", redstone: " + e.getValue().getRedstone() + ", physics: " + e.getValue().getPhysics()).send(sender)
);
});
}

@SubCommand(value = "chunkentities", permission = "yasui.profiler")
public void commandChunkEntities(CommandSender sender, Arguments args) {
World world = getWorld(sender, args);
List<Entity> entities = world.getEntities();
Multimap<ChunkCoordinate, EntityType> entitiesMap = entities.stream().collect(Multimaps.toMultimap(ChunkCoordinate::of, Entity::getType, () -> Multimaps.newMultimap(new HashMap<>(), () -> EnumMultiset.create(EntityType.class))));

List<Block> blocks = NmsUtils.getTileEntities(world);
Multimap<ChunkCoordinate, Material> tileEntitiesMap = blocks.stream().collect(Multimaps.toMultimap(ChunkCoordinate::of, Block::getType, () -> Multimaps.newMultimap(new HashMap<>(), () -> EnumMultiset.create(Material.class))));

Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
msg(sender, "user.profiler.header_entity", plugin.config.profiler_entity_chunk_count);
entitiesMap.asMap().entrySet().stream().sorted(Comparator.comparing(e -> -e.getValue().size())).limit(plugin.config.profiler_entity_chunk_count).forEach(e -> {
new Message("").append(e.getKey().getComponent()).append(I18n.format("user.chunk.total", e.getValue().size())).send(sender);
EnumMultiset<EntityType> values = EnumMultiset.create(EntityType.class);
values.addAll(e.getValue());
values.entrySet().stream().sorted(Comparator.comparingInt(v -> -v.getCount())).forEach(
v -> msg(sender, "user.profiler.entity", v.getElement().name(), v.getCount())
);
});
msg(sender, "user.profiler.header_tileentity", plugin.config.profiler_entity_chunk_count);
tileEntitiesMap.asMap().entrySet().stream().sorted(Comparator.comparing(e -> -e.getValue().size())).limit(plugin.config.profiler_entity_chunk_count).forEach(e -> {
new Message("").append(e.getKey().getComponent()).append(I18n.format("user.chunk.total", e.getValue().size())).send(sender);
EnumMultiset<Material> values = EnumMultiset.create(Material.class);
values.addAll(e.getValue());
values.entrySet().stream().sorted(Comparator.comparingInt(v -> -v.getCount())).forEach(
v -> msg(sender, "user.profiler.tileentity", v.getElement().name(), v.getCount())
);
});
});
}

private World getWorld(CommandSender sender, Arguments args) {
World world;
if (args.top() == null) {
world = asPlayer(sender).getWorld();
} else {
String worldName = args.nextString();
world = Bukkit.getWorld(worldName);
if (world == null) {
throw new BadCommandException("user.error.bad_world", worldName);
}
}
return world;
}
}
6 changes: 6 additions & 0 deletions src/main/java/cat/nyaa/yasui/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ public class Configuration extends PluginConfigure {
public int entity_limit_per_chunk_max = 50;
@Serializable(name = "entity_limit.global_enable")
public boolean entity_limit_global_enable = true;
@Serializable(name = "profiler.listen_event")
public boolean profiler_listen_event = true;
@Serializable(name = "profiler.event_chunk_count")
public int profiler_event_chunk_count = 20;
@Serializable(name = "profiler.entity_chunk_count")
public int profiler_entity_chunk_count = 10;

public Configuration(Yasui plugin) {
this.plugin = plugin;
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/cat/nyaa/yasui/ProfilerListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cat.nyaa.yasui;

import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockRedstoneEvent;

import java.util.Map;

public class ProfilerListener implements Listener {
final private Yasui plugin;

public ProfilerListener(Yasui pl) {
plugin = pl;
Bukkit.getPluginManager().registerEvents(this, plugin);
}

@EventHandler(priority = EventPriority.MONITOR)
public void onBlockPhysicsEvent(BlockPhysicsEvent event) {
ProfilerStatsMonitor.ChunkStat stat = getStat(event);
if (stat == null) return;
stat.incPhysics();
}

@EventHandler(priority = EventPriority.MONITOR)
public void onBlockRedstoneEvent(BlockRedstoneEvent event) {
ProfilerStatsMonitor.ChunkStat stat = getStat(event);
if (stat == null) return;
stat.incRedstone();
}

private ProfilerStatsMonitor.ChunkStat getStat(BlockEvent event) {
Chunk chunk = event.getBlock().getChunk();
if (chunk == null) return null;
Map<ChunkCoordinate, ProfilerStatsMonitor.ChunkStat> currentRedstoneStats = plugin.profilerStatsMonitor.currentRedstoneStats(chunk.getWorld());
return currentRedstoneStats.computeIfAbsent(ChunkCoordinate.of(chunk.getX(), chunk.getZ()), (k) -> new ProfilerStatsMonitor.ChunkStat());
}
}
81 changes: 81 additions & 0 deletions src/main/java/cat/nyaa/yasui/ProfilerStatsMonitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package cat.nyaa.yasui;

import cat.nyaa.nyaacore.Pair;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitRunnable;

import java.util.*;

public class ProfilerStatsMonitor extends BukkitRunnable {
private final Yasui plugin;
private final long startTickMillis;
private final long startTickNano;
private long currentTickMillis;
private Map<World, Deque<Pair<Long, Map<ChunkCoordinate, ChunkStat>>>> stats = new HashMap<>(600);

public ProfilerStatsMonitor(Yasui pl) {
plugin = pl;
plugin.profilerStatsMonitor = this;
startTickMillis = System.currentTimeMillis();
startTickNano = System.nanoTime();
runTaskTimer(plugin, 0, 0);
}

@Override
public void run() {
currentTickMillis = startTickMillis + (System.nanoTime() - startTickNano) / 1000000;
List<World> worlds = Bukkit.getWorlds();
for (World world : worlds) {
Deque<Pair<Long, Map<ChunkCoordinate, ChunkStat>>> worldStats = stats.computeIfAbsent(world, ignored -> new ArrayDeque<>());
if (worldStats.size() == 600) {
worldStats.poll();
}
Pair<Long, Map<ChunkCoordinate, ChunkStat>> rsCounterNode = Pair.of(currentTickMillis, new LinkedHashMap<>());
worldStats.add(rsCounterNode);
}
for (World world : stats.keySet()) {
if (!worlds.contains(world)) {
stats.remove(world);
}
}
}

public Map<ChunkCoordinate, ChunkStat> currentRedstoneStats(World world) {
return getRedstoneStats(world).getLast().getValue();
}

public Deque<Pair<Long, Map<ChunkCoordinate, ChunkStat>>> getRedstoneStats(World world) {
return stats.get(world);
}

public long getCurrentTickMillis() {
return currentTickMillis;
}

static class ChunkStat {
private int redstone = 0;
private int physics = 0;

public int incRedstone() {
return ++redstone;
}

public int incPhysics() {
return ++physics;
}

public void add(ChunkStat value) {
redstone += value.redstone;
physics += value.physics;
}

public int getPhysics() {
return physics;
}

public int getRedstone() {
return redstone;
}
}
}
6 changes: 6 additions & 0 deletions src/main/java/cat/nyaa/yasui/Yasui.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public final class Yasui extends JavaPlugin {
public TPSMonitor tpsMonitor;
public EntityListener entityListener;
public Set<String> entityLimitWorlds = new HashSet<>();
public ProfilerStatsMonitor profilerStatsMonitor;
public ProfilerListener profilerListener;

@Override
public void onEnable() {
Expand All @@ -31,6 +33,10 @@ public void onEnable() {
getCommand("yasui").setTabCompleter(commandHandler);
tpsMonitor = new TPSMonitor(this);
entityListener = new EntityListener(this);
if (config.profiler_listen_event) {
profilerStatsMonitor = new ProfilerStatsMonitor(this);
profilerListener = new ProfilerListener(this);
}
}

@Override
Expand Down
13 changes: 13 additions & 0 deletions src/main/resources/lang/en_US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ internal:
command_complete: "Command Executed!"
usage_prompt: "Usage: %s"
user:
error:
bad_world: Unknown world %s
chunk:
coord: "Chunk: x %d, z %d"
total: ", total: %d"
hover: "x:%d~%d, z:%d~%d, click to teleport to chunk center"
tp: "/minecraft:tp %d 255 %d"
profiler:
not_enabled: Not enabled
header_entity: Top %d Chunks by Count of Entities
header_tileentity: Top %d Chunks by Count of TileEntities
entity: " Entity: %s, count: %d"
tileentity: " TileEntity: %s, count: %d"
status:
line_0: "World | Entities | disable AI | random tick | entity limit"
line_1: "%s %d %s %d %s"
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ commands:
yasui:
permissions:
yasui.admin:
default: op
yasui.profiler:
default: op

0 comments on commit 870364a

Please sign in to comment.