mirror of
https://github.com/StarWishsama/Slimefun4.git
synced 2024-09-19 19:25:48 +00:00
Added a better profiler
This commit is contained in:
parent
0aa99df8c2
commit
d82b66a842
@ -40,6 +40,7 @@
|
|||||||
* (1.16+) Added Warped and Crimson Fungus to the fuel list for the Bio Generator
|
* (1.16+) Added Warped and Crimson Fungus to the fuel list for the Bio Generator
|
||||||
* Added an AoE damage effect to the Explosive Bow
|
* Added an AoE damage effect to the Explosive Bow
|
||||||
* Added runtime deprecation warnings for ItemHandlers and Attributes used by Addons
|
* Added runtime deprecation warnings for ItemHandlers and Attributes used by Addons
|
||||||
|
* Added a proper lag profiler
|
||||||
|
|
||||||
#### Changes
|
#### Changes
|
||||||
* Coolant Cells now last twice as long
|
* Coolant Cells now last twice as long
|
||||||
@ -54,6 +55,7 @@
|
|||||||
* Small performance improvements to the Cargo Net
|
* Small performance improvements to the Cargo Net
|
||||||
* Slimefun no longer supports CraftBukkit
|
* Slimefun no longer supports CraftBukkit
|
||||||
* Item Energy is now also stored persistently via NBT
|
* Item Energy is now also stored persistently via NBT
|
||||||
|
* General performance improvements for ticking blocks
|
||||||
|
|
||||||
#### Fixes
|
#### Fixes
|
||||||
* Fixed #2005
|
* Fixed #2005
|
||||||
@ -72,6 +74,9 @@
|
|||||||
* Fixed Grappling hooks making Bat sounds
|
* Fixed Grappling hooks making Bat sounds
|
||||||
* Fixed #1959
|
* Fixed #1959
|
||||||
* Fixed Melon Juice requiring Melons instead of Melon Slices
|
* Fixed Melon Juice requiring Melons instead of Melon Slices
|
||||||
|
* Fixed Cargo networks not showing up in /sf timings
|
||||||
|
* Fixed /sf timings reporting slightly inaccurate timings
|
||||||
|
* Fixed concurrency-related issues with the profiling
|
||||||
|
|
||||||
## Release Candidate 13 (16 Jun 2020)
|
## Release Candidate 13 (16 Jun 2020)
|
||||||
https://thebusybiscuit.github.io/builds/TheBusyBiscuit/Slimefun4/stable/#13
|
https://thebusybiscuit.github.io/builds/TheBusyBiscuit/Slimefun4/stable/#13
|
||||||
|
@ -26,7 +26,8 @@ class TimingsCommand extends SubCommand {
|
|||||||
@Override
|
@Override
|
||||||
public void onExecute(CommandSender sender, String[] args) {
|
public void onExecute(CommandSender sender, String[] args) {
|
||||||
if (sender.hasPermission("slimefun.command.timings") || sender instanceof ConsoleCommandSender) {
|
if (sender.hasPermission("slimefun.command.timings") || sender instanceof ConsoleCommandSender) {
|
||||||
SlimefunPlugin.getTickerTask().info(sender);
|
sender.sendMessage("Please wait a second... The results are coming in!");
|
||||||
|
SlimefunPlugin.getProfiler().requestSummary(sender);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SlimefunPlugin.getLocalization().sendMessage(sender, "messages.no-permission", true);
|
SlimefunPlugin.getLocalization().sendMessage(sender, "messages.no-permission", true);
|
||||||
|
@ -166,7 +166,7 @@ public class CargoNet extends ChestTerminalNetwork {
|
|||||||
destinations.addAll(output16);
|
destinations.addAll(output16);
|
||||||
}
|
}
|
||||||
|
|
||||||
Slimefun.runSync(() -> run(b, destinations, output));
|
run(b, destinations, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ public class EnergyNet extends Network {
|
|||||||
Set<Location> exploded = new HashSet<>();
|
Set<Location> exploded = new HashSet<>();
|
||||||
|
|
||||||
for (Location source : generators) {
|
for (Location source : generators) {
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = SlimefunPlugin.getProfiler().newEntry();
|
||||||
SlimefunItem item = BlockStorage.check(source);
|
SlimefunItem item = BlockStorage.check(source);
|
||||||
|
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
@ -247,7 +247,7 @@ public class EnergyNet extends Network {
|
|||||||
new ErrorReport(t, source, item);
|
new ErrorReport(t, source, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
SlimefunPlugin.getTickerTask().addBlockTimings(source, System.currentTimeMillis() - timestamp);
|
SlimefunPlugin.getProfiler().closeEntry(source, item, timestamp);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// This block seems to be gone now, better remove it to be extra safe
|
// This block seems to be gone now, better remove it to be extra safe
|
||||||
|
@ -56,7 +56,7 @@ class GitHubTask implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requests >= MAX_REQUESTS_PER_MINUTE) {
|
if (requests >= MAX_REQUESTS_PER_MINUTE && SlimefunPlugin.instance != null && SlimefunPlugin.instance.isEnabled()) {
|
||||||
// Slow down API requests and wait a minute after more than x requests were made
|
// Slow down API requests and wait a minute after more than x requests were made
|
||||||
Bukkit.getScheduler().runTaskLaterAsynchronously(SlimefunPlugin.instance, this::grabTextures, 2 * 60 * 20L);
|
Bukkit.getScheduler().runTaskLaterAsynchronously(SlimefunPlugin.instance, this::grabTextures, 2 * 60 * 20L);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ class PlaceholderAPIHook extends PlaceholderExpansion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params.equals("timings_lag")) {
|
if (params.equals("timings_lag")) {
|
||||||
return SlimefunPlugin.getTickerTask().getTime() + "ms";
|
return SlimefunPlugin.getProfiler().getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.equals("language")) {
|
if (params.equals("language")) {
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package io.github.thebusybiscuit.slimefun4.core.services.profiler;
|
||||||
|
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
|
||||||
|
import io.github.thebusybiscuit.cscorelib2.blocks.BlockPosition;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon;
|
||||||
|
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem;
|
||||||
|
|
||||||
|
class ProfiledBlock {
|
||||||
|
|
||||||
|
private final BlockPosition position;
|
||||||
|
private final SlimefunItem item;
|
||||||
|
|
||||||
|
ProfiledBlock(BlockPosition position, SlimefunItem item) {
|
||||||
|
this.position = position;
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfiledBlock(Block b) {
|
||||||
|
this(new BlockPosition(b), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockPosition getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return item.getID();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlimefunAddon getAddon() {
|
||||||
|
return item.getAddon();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof ProfiledBlock) {
|
||||||
|
return position.equals(((ProfiledBlock) obj).position);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return position.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,317 @@
|
|||||||
|
package io.github.thebusybiscuit.slimefun4.core.services.profiler;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.Validate;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import io.github.thebusybiscuit.cscorelib2.blocks.BlockPosition;
|
||||||
|
import io.github.thebusybiscuit.cscorelib2.chat.ChatColors;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.implementation.tasks.TickerTask;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.utils.NumberUtils;
|
||||||
|
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem;
|
||||||
|
import me.mrCookieSlime.Slimefun.api.Slimefun;
|
||||||
|
import net.md_5.bungee.api.chat.HoverEvent;
|
||||||
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SlimefunProfiler} works closely to the {@link TickerTask} and is responsible for
|
||||||
|
* monitoring that task.
|
||||||
|
* It collects timings data for any ticked {@link Block} and the corresponding {@link SlimefunItem}.
|
||||||
|
* This allows developers to identify laggy {@link SlimefunItem SlimefunItems} or {@link SlimefunAddon SlimefunAddons}.
|
||||||
|
* But it also enabled Server Admins to locate lag-inducing areas on the {@link Server}.
|
||||||
|
*
|
||||||
|
* @author TheBusyBiscuit
|
||||||
|
*
|
||||||
|
* @see TickerTask
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SlimefunProfiler {
|
||||||
|
|
||||||
|
// The threshold at which a Block or Chunk is significant enough to appear in /sf timings
|
||||||
|
private static final int VISIBILITY_THRESHOLD = 275_000;
|
||||||
|
|
||||||
|
private final ExecutorService executor = Executors.newFixedThreadPool(3);
|
||||||
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
|
private final AtomicInteger queued = new AtomicInteger(0);
|
||||||
|
|
||||||
|
private long totalElapsedTime;
|
||||||
|
|
||||||
|
private final Map<ProfiledBlock, Long> timings = new ConcurrentHashMap<>();
|
||||||
|
private final Queue<CommandSender> requests = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method starts the profiling, data from previous runs will be cleared.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
running.set(true);
|
||||||
|
queued.set(0);
|
||||||
|
timings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method starts a new profiler entry.
|
||||||
|
*
|
||||||
|
* @return A timestamp, best fed back into {@link #closeEntry(Location, SlimefunItem, long)}
|
||||||
|
*/
|
||||||
|
public long newEntry() {
|
||||||
|
if (!running.get()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
queued.incrementAndGet();
|
||||||
|
return System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method closes a previously started entry.
|
||||||
|
* Make sure to call {@link #newEntry()} to get the timestamp in advance.
|
||||||
|
*
|
||||||
|
* @param l
|
||||||
|
* The {@link Location} of our {@link Block}
|
||||||
|
* @param item
|
||||||
|
* The {@link SlimefunItem} at this {@link Location}
|
||||||
|
* @param timestamp
|
||||||
|
* The timestamp marking the start of this entry, you can retrieve it using {@link #newEntry()}
|
||||||
|
*/
|
||||||
|
public void closeEntry(Location l, SlimefunItem item, long timestamp) {
|
||||||
|
if (timestamp == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsedTime = System.nanoTime() - timestamp;
|
||||||
|
|
||||||
|
executor.execute(() -> {
|
||||||
|
ProfiledBlock block = new ProfiledBlock(new BlockPosition(l), item);
|
||||||
|
timings.put(block, elapsedTime);
|
||||||
|
queued.decrementAndGet();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This stops the profiling.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
running.set(false);
|
||||||
|
|
||||||
|
if (SlimefunPlugin.instance == null || !SlimefunPlugin.instance.isEnabled()) {
|
||||||
|
// Slimefun has been disabled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we got more than one Thread in our pool, blocking this one is completely fine
|
||||||
|
executor.execute(() -> {
|
||||||
|
|
||||||
|
// Wait for all timing results to come in
|
||||||
|
while (queued.get() > 0 && !running.get()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
|
Slimefun.getLogger().log(Level.SEVERE, "A waiting Thread was interrupted", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (running.get()) {
|
||||||
|
// Looks like the next profiling has already started, abort!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalElapsedTime = timings.values().stream().mapToLong(Long::longValue).sum();
|
||||||
|
|
||||||
|
Iterator<CommandSender> iterator = requests.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
sendSummary(iterator.next());
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method requests a summary for the given {@link CommandSender}.
|
||||||
|
* The summary will be sent upon the next available moment in time.
|
||||||
|
*
|
||||||
|
* @param sender
|
||||||
|
* The {@link CommandSender} who shall receive this summary.
|
||||||
|
*/
|
||||||
|
public void requestSummary(CommandSender sender) {
|
||||||
|
requests.add(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Long> getByItem() {
|
||||||
|
Map<String, Long> map = new HashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<ProfiledBlock, Long> entry : timings.entrySet()) {
|
||||||
|
map.merge(entry.getKey().getId(), entry.getValue(), Long::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Long> getByChunk() {
|
||||||
|
Map<String, Long> map = new HashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<ProfiledBlock, Long> entry : timings.entrySet()) {
|
||||||
|
String world = entry.getKey().getPosition().getWorld().getName();
|
||||||
|
int x = entry.getKey().getPosition().getChunkX();
|
||||||
|
int z = entry.getKey().getPosition().getChunkZ();
|
||||||
|
|
||||||
|
map.merge(world + " (" + x + ',' + z + ')', entry.getValue(), Long::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getBlocksInChunk(String chunk) {
|
||||||
|
int blocks = 0;
|
||||||
|
|
||||||
|
for (ProfiledBlock block : timings.keySet()) {
|
||||||
|
String world = block.getPosition().getWorld().getName();
|
||||||
|
int x = block.getPosition().getChunkX();
|
||||||
|
int z = block.getPosition().getChunkZ();
|
||||||
|
|
||||||
|
if (chunk.equals(world + " (" + x + ',' + z + ')')) {
|
||||||
|
blocks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getBlocks(String id) {
|
||||||
|
int blocks = 0;
|
||||||
|
|
||||||
|
for (ProfiledBlock block : timings.keySet()) {
|
||||||
|
if (block.getId().equals(id)) {
|
||||||
|
blocks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendSummary(CommandSender sender) {
|
||||||
|
Map<String, Long> chunks = getByChunk();
|
||||||
|
Map<String, Long> machines = getByItem();
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColors.color("&2== &aSlimefun Lag Profiler &2=="));
|
||||||
|
sender.sendMessage(ChatColors.color("&6Running: &e&l" + String.valueOf(!SlimefunPlugin.getTickerTask().isHalted()).toUpperCase(Locale.ROOT)));
|
||||||
|
sender.sendMessage("");
|
||||||
|
sender.sendMessage(ChatColors.color("&6Impact: &e" + NumberUtils.getAsMillis(totalElapsedTime)));
|
||||||
|
sender.sendMessage(ChatColors.color("&6Ticked Chunks: &e" + chunks.size()));
|
||||||
|
sender.sendMessage(ChatColors.color("&6Ticked Blocks: &e" + timings.size()));
|
||||||
|
sender.sendMessage("");
|
||||||
|
sender.sendMessage(ChatColors.color("&6Ticking Machines:"));
|
||||||
|
|
||||||
|
summarizeTimings(sender, entry -> {
|
||||||
|
int count = getBlocks(entry.getKey());
|
||||||
|
String time = NumberUtils.getAsMillis(entry.getValue());
|
||||||
|
String average = NumberUtils.getAsMillis(entry.getValue() / count);
|
||||||
|
|
||||||
|
return entry.getKey() + " - " + count + "x (" + time + ", " + average + " avg/block)";
|
||||||
|
}, machines.entrySet().stream());
|
||||||
|
|
||||||
|
sender.sendMessage("");
|
||||||
|
sender.sendMessage(ChatColors.color("&6Ticking Chunks:"));
|
||||||
|
|
||||||
|
summarizeTimings(sender, entry -> {
|
||||||
|
int count = getBlocksInChunk(entry.getKey());
|
||||||
|
String time = NumberUtils.getAsMillis(entry.getValue());
|
||||||
|
|
||||||
|
return entry.getKey() + " - " + count + "x (" + time + ")";
|
||||||
|
}, chunks.entrySet().stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void summarizeTimings(CommandSender sender, Function<Map.Entry<String, Long>, String> formatter, Stream<Map.Entry<String, Long>> stream) {
|
||||||
|
List<Entry<String, Long>> results = stream.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (sender instanceof Player) {
|
||||||
|
TextComponent component = new TextComponent(" Hover for more details...");
|
||||||
|
component.setColor(net.md_5.bungee.api.ChatColor.GRAY);
|
||||||
|
component.setItalic(true);
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
int hidden = 0;
|
||||||
|
|
||||||
|
for (Map.Entry<String, Long> entry : results) {
|
||||||
|
if (entry.getValue() > VISIBILITY_THRESHOLD) {
|
||||||
|
builder.append("\n&e").append(formatter.apply(entry));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hidden++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("\n\n&c+ &6").append(hidden).append(" more");
|
||||||
|
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(ChatColors.color(builder.toString()))));
|
||||||
|
sender.spigot().sendMessage(component);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int hidden = 0;
|
||||||
|
|
||||||
|
for (Map.Entry<String, Long> entry : results) {
|
||||||
|
if (entry.getValue() > VISIBILITY_THRESHOLD) {
|
||||||
|
sender.sendMessage(" " + ChatColor.stripColor(formatter.apply(entry)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hidden++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage("+ " + hidden + " more");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTime() {
|
||||||
|
return NumberUtils.getAsMillis(totalElapsedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTime(Block b) {
|
||||||
|
Validate.notNull("Cannot get timings for a null Block");
|
||||||
|
|
||||||
|
long time = timings.getOrDefault(new ProfiledBlock(b), 0L);
|
||||||
|
return NumberUtils.getAsMillis(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTime(Chunk chunk) {
|
||||||
|
Validate.notNull("Cannot get timings for a null Chunk");
|
||||||
|
|
||||||
|
long time = getByChunk().getOrDefault(chunk.getWorld().getName() + " (" + chunk.getX() + ',' + chunk.getZ() + ')', 0L);
|
||||||
|
return NumberUtils.getAsMillis(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTime(SlimefunItem item) {
|
||||||
|
Validate.notNull("Cannot get timings for a null SlimefunItem");
|
||||||
|
|
||||||
|
long time = getByItem().getOrDefault(item.getID(), 0L);
|
||||||
|
return NumberUtils.getAsMillis(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* This package holds classes related to the
|
||||||
|
* {@link io.github.thebusybiscuit.slimefun4.core.services.profiler.SlimefunProfiler}.
|
||||||
|
* The {@link io.github.thebusybiscuit.slimefun4.core.services.profiler.SlimefunProfiler} is used to determine
|
||||||
|
* {@link org.bukkit.block.Block Blocks}, {@link org.bukkit.Chunk Chunks} or
|
||||||
|
* {@link me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem SlimefunItems} that cause lag or performance
|
||||||
|
* drops.
|
||||||
|
*/
|
||||||
|
package io.github.thebusybiscuit.slimefun4.core.services.profiler;
|
@ -43,6 +43,7 @@ import io.github.thebusybiscuit.slimefun4.core.services.UpdaterService;
|
|||||||
import io.github.thebusybiscuit.slimefun4.core.services.github.GitHubService;
|
import io.github.thebusybiscuit.slimefun4.core.services.github.GitHubService;
|
||||||
import io.github.thebusybiscuit.slimefun4.core.services.metrics.MetricsService;
|
import io.github.thebusybiscuit.slimefun4.core.services.metrics.MetricsService;
|
||||||
import io.github.thebusybiscuit.slimefun4.core.services.plugins.ThirdPartyPluginService;
|
import io.github.thebusybiscuit.slimefun4.core.services.plugins.ThirdPartyPluginService;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.core.services.profiler.SlimefunProfiler;
|
||||||
import io.github.thebusybiscuit.slimefun4.implementation.items.altar.AncientAltar;
|
import io.github.thebusybiscuit.slimefun4.implementation.items.altar.AncientAltar;
|
||||||
import io.github.thebusybiscuit.slimefun4.implementation.items.backpacks.Cooler;
|
import io.github.thebusybiscuit.slimefun4.implementation.items.backpacks.Cooler;
|
||||||
import io.github.thebusybiscuit.slimefun4.implementation.items.electric.BasicCircuitBoard;
|
import io.github.thebusybiscuit.slimefun4.implementation.items.electric.BasicCircuitBoard;
|
||||||
@ -126,6 +127,7 @@ public final class SlimefunPlugin extends JavaPlugin implements SlimefunAddon {
|
|||||||
private final PerWorldSettingsService worldSettingsService = new PerWorldSettingsService(this);
|
private final PerWorldSettingsService worldSettingsService = new PerWorldSettingsService(this);
|
||||||
private final ThirdPartyPluginService thirdPartySupportService = new ThirdPartyPluginService(this);
|
private final ThirdPartyPluginService thirdPartySupportService = new ThirdPartyPluginService(this);
|
||||||
private final MinecraftRecipeService recipeService = new MinecraftRecipeService(this);
|
private final MinecraftRecipeService recipeService = new MinecraftRecipeService(this);
|
||||||
|
private final SlimefunProfiler profiler = new SlimefunProfiler();
|
||||||
private LocalizationService local;
|
private LocalizationService local;
|
||||||
|
|
||||||
private GPSNetwork gpsNetwork;
|
private GPSNetwork gpsNetwork;
|
||||||
@ -612,6 +614,10 @@ public final class SlimefunPlugin extends JavaPlugin implements SlimefunAddon {
|
|||||||
return instance.command;
|
return instance.command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SlimefunProfiler getProfiler() {
|
||||||
|
return instance.profiler;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This returns the currently installed version of Minecraft.
|
* This returns the currently installed version of Minecraft.
|
||||||
*
|
*
|
||||||
|
@ -41,7 +41,7 @@ public class CargoManager extends SlimefunItem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSynchronized() {
|
public boolean isSynchronized() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}, new BlockUseHandler() {
|
}, new BlockUseHandler() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package io.github.thebusybiscuit.slimefun4.implementation.listeners;
|
package io.github.thebusybiscuit.slimefun4.implementation.listeners;
|
||||||
|
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.Skull;
|
import org.bukkit.block.Skull;
|
||||||
@ -15,12 +14,11 @@ import org.bukkit.inventory.EquipmentSlot;
|
|||||||
|
|
||||||
import io.github.thebusybiscuit.cscorelib2.chat.ChatColors;
|
import io.github.thebusybiscuit.cscorelib2.chat.ChatColors;
|
||||||
import io.github.thebusybiscuit.cscorelib2.skull.SkullBlock;
|
import io.github.thebusybiscuit.cscorelib2.skull.SkullBlock;
|
||||||
|
import io.github.thebusybiscuit.slimefun4.core.attributes.EnergyNetComponent;
|
||||||
import io.github.thebusybiscuit.slimefun4.core.networks.energy.EnergyNet;
|
import io.github.thebusybiscuit.slimefun4.core.networks.energy.EnergyNet;
|
||||||
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunItems;
|
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunItems;
|
||||||
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin;
|
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin;
|
||||||
import io.github.thebusybiscuit.slimefun4.implementation.tasks.TickerTask;
|
|
||||||
import io.github.thebusybiscuit.slimefun4.utils.HeadTexture;
|
import io.github.thebusybiscuit.slimefun4.utils.HeadTexture;
|
||||||
import io.github.thebusybiscuit.slimefun4.utils.NumberUtils;
|
|
||||||
import io.github.thebusybiscuit.slimefun4.utils.SlimefunUtils;
|
import io.github.thebusybiscuit.slimefun4.utils.SlimefunUtils;
|
||||||
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem;
|
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem;
|
||||||
import me.mrCookieSlime.Slimefun.api.BlockStorage;
|
import me.mrCookieSlime.Slimefun.api.BlockStorage;
|
||||||
@ -28,14 +26,14 @@ import me.mrCookieSlime.Slimefun.api.energy.ChargableBlock;
|
|||||||
|
|
||||||
public class DebugFishListener implements Listener {
|
public class DebugFishListener implements Listener {
|
||||||
|
|
||||||
private final String enabledTooltip;
|
private final String greenCheckmark;
|
||||||
private final String disabledTooltip;
|
private final String redCross;
|
||||||
|
|
||||||
public DebugFishListener(SlimefunPlugin plugin) {
|
public DebugFishListener(SlimefunPlugin plugin) {
|
||||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
|
||||||
enabledTooltip = "&2\u2714";
|
greenCheckmark = "&2\u2714";
|
||||||
disabledTooltip = "&4\u2718";
|
redCross = "&4\u2718";
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
@ -81,7 +79,7 @@ public class DebugFishListener implements Listener {
|
|||||||
p.sendMessage(ChatColors.color("&dPlugin: " + "&e" + item.getAddon().getName()));
|
p.sendMessage(ChatColors.color("&dPlugin: " + "&e" + item.getAddon().getName()));
|
||||||
|
|
||||||
if (b.getState() instanceof Skull) {
|
if (b.getState() instanceof Skull) {
|
||||||
p.sendMessage(ChatColors.color("&dSkull: " + enabledTooltip));
|
p.sendMessage(ChatColors.color("&dSkull: " + greenCheckmark));
|
||||||
|
|
||||||
// Check if the skull is a wall skull, and if so use Directional instead of Rotatable.
|
// Check if the skull is a wall skull, and if so use Directional instead of Rotatable.
|
||||||
if (b.getType() == Material.PLAYER_WALL_HEAD) {
|
if (b.getType() == Material.PLAYER_WALL_HEAD) {
|
||||||
@ -93,40 +91,40 @@ public class DebugFishListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (BlockStorage.getStorage(b.getWorld()).hasInventory(b.getLocation())) {
|
if (BlockStorage.getStorage(b.getWorld()).hasInventory(b.getLocation())) {
|
||||||
p.sendMessage(ChatColors.color("&dInventory: " + enabledTooltip));
|
p.sendMessage(ChatColors.color("&dInventory: " + greenCheckmark));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
p.sendMessage(ChatColors.color("&dInventory: " + disabledTooltip));
|
p.sendMessage(ChatColors.color("&dInventory: " + redCross));
|
||||||
}
|
}
|
||||||
|
|
||||||
TickerTask ticker = SlimefunPlugin.getTickerTask();
|
|
||||||
|
|
||||||
if (item.isTicking()) {
|
if (item.isTicking()) {
|
||||||
p.sendMessage(ChatColors.color("&dTicker: " + enabledTooltip));
|
p.sendMessage(ChatColors.color("&dTicker: " + greenCheckmark));
|
||||||
p.sendMessage(ChatColors.color(" &dAsync: &e" + (BlockStorage.check(b).getBlockTicker().isSynchronized() ? disabledTooltip : enabledTooltip)));
|
p.sendMessage(ChatColors.color(" &dAsync: &e" + (item.getBlockTicker().isSynchronized() ? redCross : greenCheckmark)));
|
||||||
p.sendMessage(ChatColors.color(" &dTimings: &e" + NumberUtils.getAsMillis(ticker.getTimings(b))));
|
p.sendMessage(ChatColors.color(" &dTimings: &e" + SlimefunPlugin.getProfiler().getTime(b)));
|
||||||
p.sendMessage(ChatColors.color(" &dTotal Timings: &e" + NumberUtils.getAsMillis(ticker.getTimings(BlockStorage.checkID(b)))));
|
p.sendMessage(ChatColors.color(" &dTotal Timings: &e" + SlimefunPlugin.getProfiler().getTime(item)));
|
||||||
p.sendMessage(ChatColors.color(" &dChunk Timings: &e" + NumberUtils.getAsMillis(ticker.getTimings(b.getChunk()))));
|
p.sendMessage(ChatColors.color(" &dChunk Timings: &e" + SlimefunPlugin.getProfiler().getTime(b.getChunk())));
|
||||||
}
|
}
|
||||||
else if (item.getEnergyTicker() != null) {
|
else if (item.getEnergyTicker() != null) {
|
||||||
p.sendMessage(ChatColors.color("&dTicking: " + "&3Indirect"));
|
p.sendMessage(ChatColors.color("&dTicking: " + "&3Indirect"));
|
||||||
p.sendMessage(ChatColors.color(" &dTimings: &e" + NumberUtils.getAsMillis(ticker.getTimings(b))));
|
p.sendMessage(ChatColors.color(" &dTimings: &e" + SlimefunPlugin.getProfiler().getTime(b)));
|
||||||
p.sendMessage(ChatColors.color(" &dChunk Timings: &e" + NumberUtils.getAsMillis(ticker.getTimings(b.getChunk()))));
|
p.sendMessage(ChatColors.color(" &dChunk Timings: &e" + SlimefunPlugin.getProfiler().getTime(b.getChunk())));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
p.sendMessage(ChatColors.color("&dTicker: " + disabledTooltip));
|
p.sendMessage(ChatColors.color("&dTicker: " + redCross));
|
||||||
p.sendMessage(ChatColor.translateAlternateColorCodes('&', "&dTicking: " + disabledTooltip));
|
p.sendMessage(ChatColors.color("&dTicking: " + redCross));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ChargableBlock.isChargable(b)) {
|
if (ChargableBlock.isChargable(b)) {
|
||||||
p.sendMessage(ChatColors.color("&dChargeable: " + enabledTooltip));
|
p.sendMessage(ChatColors.color("&dChargeable: " + greenCheckmark));
|
||||||
p.sendMessage(ChatColors.color(" &dEnergy: &e" + ChargableBlock.getCharge(b) + " / " + ChargableBlock.getMaxCharge(b)));
|
p.sendMessage(ChatColors.color(" &dEnergy: &e" + ChargableBlock.getCharge(b) + " / " + ChargableBlock.getMaxCharge(b)));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
p.sendMessage(ChatColors.color("&dChargeable: " + disabledTooltip));
|
p.sendMessage(ChatColors.color("&dChargeable: " + redCross));
|
||||||
}
|
}
|
||||||
|
|
||||||
p.sendMessage(ChatColors.color(" &dEnergyNet Type: &e" + EnergyNet.getComponent(b.getLocation())));
|
if (item instanceof EnergyNetComponent) {
|
||||||
|
p.sendMessage(ChatColors.color(" &dEnergyNet Type: &e" + EnergyNet.getComponent(b.getLocation())));
|
||||||
|
}
|
||||||
|
|
||||||
p.sendMessage(ChatColors.color("&6" + BlockStorage.getBlockInfoAsJson(b)));
|
p.sendMessage(ChatColors.color("&6" + BlockStorage.getBlockInfoAsJson(b)));
|
||||||
p.sendMessage(" ");
|
p.sendMessage(" ");
|
||||||
|
@ -1,70 +1,38 @@
|
|||||||
package io.github.thebusybiscuit.slimefun4.implementation.tasks;
|
package io.github.thebusybiscuit.slimefun4.implementation.tasks;
|
||||||
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.Chunk;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
import io.github.thebusybiscuit.cscorelib2.chat.ChatColors;
|
|
||||||
import io.github.thebusybiscuit.slimefun4.api.ErrorReport;
|
import io.github.thebusybiscuit.slimefun4.api.ErrorReport;
|
||||||
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin;
|
import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin;
|
||||||
import io.github.thebusybiscuit.slimefun4.utils.NumberUtils;
|
|
||||||
import io.github.thebusybiscuit.slimefun4.utils.PatternUtils;
|
import io.github.thebusybiscuit.slimefun4.utils.PatternUtils;
|
||||||
|
import me.mrCookieSlime.CSCoreLibPlugin.Configuration.Config;
|
||||||
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem;
|
import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem;
|
||||||
import me.mrCookieSlime.Slimefun.Objects.handlers.BlockTicker;
|
import me.mrCookieSlime.Slimefun.Objects.handlers.BlockTicker;
|
||||||
import me.mrCookieSlime.Slimefun.api.BlockStorage;
|
import me.mrCookieSlime.Slimefun.api.BlockStorage;
|
||||||
import me.mrCookieSlime.Slimefun.api.Slimefun;
|
import me.mrCookieSlime.Slimefun.api.Slimefun;
|
||||||
import net.md_5.bungee.api.chat.HoverEvent;
|
|
||||||
import net.md_5.bungee.api.chat.TextComponent;
|
|
||||||
|
|
||||||
public class TickerTask implements Runnable {
|
public class TickerTask implements Runnable {
|
||||||
|
|
||||||
private static final int VISIBILITY_THRESHOLD = 225_000;
|
|
||||||
|
|
||||||
private final Set<BlockTicker> tickers = new HashSet<>();
|
private final Set<BlockTicker> tickers = new HashSet<>();
|
||||||
|
|
||||||
// These are "Queues" of blocks that need to be removed or moved
|
// These are "Queues" of blocks that need to be removed or moved
|
||||||
private final ConcurrentMap<Location, Location> movingQueue = new ConcurrentHashMap<>();
|
private final Map<Location, Location> movingQueue = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentMap<Location, Boolean> deletionQueue = new ConcurrentHashMap<>();
|
private final Map<Location, Boolean> deletionQueue = new ConcurrentHashMap<>();
|
||||||
|
private final Map<Location, Integer> buggedBlocks = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentMap<Location, Integer> buggedBlocks = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final ConcurrentMap<Location, Long> blockTimings = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentMap<String, Integer> machineCount = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentMap<String, Long> machineTimings = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private final ConcurrentMap<String, Long> chunkTimings = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentMap<String, Integer> chunkItemCount = new ConcurrentHashMap<>();
|
|
||||||
private final Set<String> skippedChunks = new HashSet<>();
|
|
||||||
|
|
||||||
private boolean halted = false;
|
private boolean halted = false;
|
||||||
|
|
||||||
private int skippedBlocks = 0;
|
|
||||||
private int chunks = 0;
|
|
||||||
private int blocks = 0;
|
|
||||||
private long time = 0;
|
|
||||||
|
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
|
|
||||||
public void abortTick() {
|
public void abortTick() {
|
||||||
@ -78,18 +46,7 @@ public class TickerTask implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
long timestamp = System.nanoTime();
|
SlimefunPlugin.getProfiler().start();
|
||||||
|
|
||||||
skippedBlocks = 0;
|
|
||||||
chunks = 0;
|
|
||||||
blocks = 0;
|
|
||||||
chunkItemCount.clear();
|
|
||||||
machineCount.clear();
|
|
||||||
time = 0;
|
|
||||||
chunkTimings.clear();
|
|
||||||
skippedChunks.clear();
|
|
||||||
machineTimings.clear();
|
|
||||||
blockTimings.clear();
|
|
||||||
|
|
||||||
Map<Location, Integer> bugs = new HashMap<>(buggedBlocks);
|
Map<Location, Integer> bugs = new HashMap<>(buggedBlocks);
|
||||||
buggedBlocks.clear();
|
buggedBlocks.clear();
|
||||||
@ -103,24 +60,23 @@ public class TickerTask implements Runnable {
|
|||||||
|
|
||||||
if (!halted) {
|
if (!halted) {
|
||||||
for (String chunk : BlockStorage.getTickingChunks()) {
|
for (String chunk : BlockStorage.getTickingChunks()) {
|
||||||
long chunkTimestamp = System.nanoTime();
|
try {
|
||||||
chunks++;
|
Set<Location> locations = BlockStorage.getTickingLocations(chunk);
|
||||||
|
String[] components = PatternUtils.SEMICOLON.split(chunk);
|
||||||
|
|
||||||
Set<Location> locations = BlockStorage.getTickingLocations(chunk);
|
World world = Bukkit.getWorld(components[0]);
|
||||||
|
int x = Integer.parseInt(components[components.length - 2]);
|
||||||
|
int z = Integer.parseInt(components[components.length - 1]);
|
||||||
|
|
||||||
for (Location l : locations) {
|
if (world != null && world.isChunkLoaded(x, z)) {
|
||||||
if (l.getWorld().isChunkLoaded(l.getBlockX() >> 4, l.getBlockZ() >> 4)) {
|
for (Location l : locations) {
|
||||||
tick(l, chunk, bugs);
|
tick(l, bugs);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
skippedBlocks += locations.size();
|
|
||||||
skippedChunks.add(chunk);
|
|
||||||
chunks--;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (ArrayIndexOutOfBoundsException | NumberFormatException x) {
|
||||||
chunkTimings.put(chunk, System.nanoTime() - chunkTimestamp);
|
Slimefun.getLogger().log(Level.SEVERE, x, () -> "An Exception has occured while trying to parse Chunk: " + chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,49 +92,27 @@ public class TickerTask implements Runnable {
|
|||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
time = System.nanoTime() - timestamp;
|
|
||||||
running = false;
|
running = false;
|
||||||
|
SlimefunPlugin.getProfiler().stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tick(Location l, String tickedChunk, Map<Location, Integer> bugs) {
|
private void tick(Location l, Map<Location, Integer> bugs) {
|
||||||
Block b = l.getBlock();
|
Config data = BlockStorage.getLocationInfo(l);
|
||||||
SlimefunItem item = BlockStorage.check(l);
|
SlimefunItem item = SlimefunItem.getByID(data.getString("id"));
|
||||||
|
|
||||||
if (item != null && item.getBlockTicker() != null) {
|
if (item != null && item.getBlockTicker() != null) {
|
||||||
blocks++;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
long timestamp = SlimefunPlugin.getProfiler().newEntry();
|
||||||
|
Block b = l.getBlock();
|
||||||
item.getBlockTicker().update();
|
item.getBlockTicker().update();
|
||||||
|
|
||||||
if (item.getBlockTicker().isSynchronized()) {
|
if (item.getBlockTicker().isSynchronized()) {
|
||||||
Slimefun.runSync(() -> {
|
// We are ignoring the timestamp from above because synchronized actions
|
||||||
try {
|
// are always ran with a 50ms delay (1 game tick)
|
||||||
long timestamp = System.nanoTime();
|
Slimefun.runSync(() -> tickBlock(bugs, l, b, item, data, System.nanoTime()));
|
||||||
item.getBlockTicker().tick(b, item, BlockStorage.getLocationInfo(l));
|
|
||||||
|
|
||||||
long machinetime = NumberUtils.getLong(machineTimings.get(item.getID()), 0);
|
|
||||||
int chunk = NumberUtils.getInt(chunkItemCount.get(tickedChunk), 0);
|
|
||||||
int machine = NumberUtils.getInt(machineCount.get(item.getID()), 0);
|
|
||||||
|
|
||||||
machineTimings.put(item.getID(), machinetime + (System.nanoTime() - timestamp));
|
|
||||||
chunkItemCount.put(tickedChunk, chunk + 1);
|
|
||||||
machineCount.put(item.getID(), machine + 1);
|
|
||||||
blockTimings.put(l, System.nanoTime() - timestamp);
|
|
||||||
}
|
|
||||||
catch (Exception | LinkageError x) {
|
|
||||||
int errors = bugs.getOrDefault(l, 0);
|
|
||||||
reportErrors(l, item, x, errors);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
long timestamp = System.nanoTime();
|
tickBlock(bugs, l, b, item, data, timestamp);
|
||||||
item.getBlockTicker().tick(b, item, BlockStorage.getLocationInfo(l));
|
|
||||||
|
|
||||||
machineTimings.merge(item.getID(), (System.nanoTime() - timestamp), Long::sum);
|
|
||||||
chunkItemCount.merge(tickedChunk, 1, Integer::sum);
|
|
||||||
machineCount.merge(item.getID(), 1, Integer::sum);
|
|
||||||
blockTimings.put(l, System.nanoTime() - timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tickers.add(item.getBlockTicker());
|
tickers.add(item.getBlockTicker());
|
||||||
@ -188,8 +122,18 @@ public class TickerTask implements Runnable {
|
|||||||
reportErrors(l, item, x, errors);
|
reportErrors(l, item, x, errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
skippedBlocks++;
|
|
||||||
|
private void tickBlock(Map<Location, Integer> bugs, Location l, Block b, SlimefunItem item, Config data, long timestamp) {
|
||||||
|
try {
|
||||||
|
item.getBlockTicker().tick(b, item, data);
|
||||||
|
}
|
||||||
|
catch (Exception | LinkageError x) {
|
||||||
|
int errors = bugs.getOrDefault(l, 0);
|
||||||
|
reportErrors(l, item, x, errors);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
SlimefunPlugin.getProfiler().closeEntry(l, item, timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,101 +159,6 @@ public class TickerTask implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTime() {
|
|
||||||
return NumberUtils.getAsMillis(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void info(CommandSender sender) {
|
|
||||||
sender.sendMessage(ChatColors.color("&2== &aSlimefun Diagnostic Tool &2=="));
|
|
||||||
sender.sendMessage(ChatColors.color("&6Halted: &e&l" + String.valueOf(halted).toUpperCase(Locale.ROOT)));
|
|
||||||
sender.sendMessage("");
|
|
||||||
sender.sendMessage(ChatColors.color("&6Impact: &e" + NumberUtils.getAsMillis(time)));
|
|
||||||
sender.sendMessage(ChatColors.color("&6Ticked Chunks: &e" + chunks));
|
|
||||||
sender.sendMessage(ChatColors.color("&6Ticked Machines: &e" + blocks));
|
|
||||||
sender.sendMessage(ChatColors.color("&6Skipped Machines: &e" + skippedBlocks));
|
|
||||||
sender.sendMessage("");
|
|
||||||
sender.sendMessage(ChatColors.color("&6Ticking Machines:"));
|
|
||||||
|
|
||||||
summarizeTimings(sender, entry -> {
|
|
||||||
int count = machineCount.get(entry.getKey());
|
|
||||||
String timings = NumberUtils.getAsMillis(entry.getValue());
|
|
||||||
String average = NumberUtils.getAsMillis(entry.getValue() / count);
|
|
||||||
|
|
||||||
return entry.getKey() + " - " + count + "x (" + timings + ", " + average + " avg/machine)";
|
|
||||||
}, machineCount.keySet().stream().map(key -> new AbstractMap.SimpleEntry<>(key, machineTimings.getOrDefault(key, 0L))));
|
|
||||||
|
|
||||||
sender.sendMessage("");
|
|
||||||
sender.sendMessage(ChatColors.color("&6Ticking Chunks:"));
|
|
||||||
|
|
||||||
summarizeTimings(sender, entry -> {
|
|
||||||
int count = chunkItemCount.getOrDefault(entry.getKey(), 0);
|
|
||||||
String timings = NumberUtils.getAsMillis(entry.getValue());
|
|
||||||
|
|
||||||
return formatChunk(entry.getKey()) + " - " + count + "x (" + timings + ")";
|
|
||||||
}, chunkTimings.entrySet().stream().filter(entry -> !skippedChunks.contains(entry.getKey())));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void summarizeTimings(CommandSender sender, Function<Map.Entry<String, Long>, String> formatter, Stream<Map.Entry<String, Long>> stream) {
|
|
||||||
List<Entry<String, Long>> timings = stream.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (sender instanceof Player) {
|
|
||||||
TextComponent component = new TextComponent(" Hover for more Info");
|
|
||||||
component.setColor(net.md_5.bungee.api.ChatColor.GRAY);
|
|
||||||
component.setItalic(true);
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
int hidden = 0;
|
|
||||||
|
|
||||||
for (Map.Entry<String, Long> entry : timings) {
|
|
||||||
if (entry.getValue() > VISIBILITY_THRESHOLD) {
|
|
||||||
builder.append("\n&c").append(formatter.apply(entry));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hidden++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append("\n\n&c+ &4").append(hidden).append(" Hidden");
|
|
||||||
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(ChatColors.color(builder.toString()))));
|
|
||||||
sender.spigot().sendMessage(component);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int hidden = 0;
|
|
||||||
|
|
||||||
for (Map.Entry<String, Long> entry : timings) {
|
|
||||||
if (entry.getValue() > VISIBILITY_THRESHOLD) {
|
|
||||||
sender.sendMessage(" " + ChatColor.stripColor(formatter.apply(entry)));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
hidden++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sender.sendMessage("+ " + hidden + " Hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatChunk(String chunk) {
|
|
||||||
String[] components = PatternUtils.SEMICOLON.split(chunk);
|
|
||||||
return components[0] + " [" + components[2] + ',' + components[3] + ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimings(Block b) {
|
|
||||||
return blockTimings.getOrDefault(b.getLocation(), 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimings(String item) {
|
|
||||||
return machineTimings.getOrDefault(item, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimings(Chunk c) {
|
|
||||||
String id = c.getWorld().getName() + ';' + c.getX() + ';' + c.getZ();
|
|
||||||
return chunkTimings.getOrDefault(id, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addBlockTimings(Location l, long time) {
|
|
||||||
blockTimings.put(l, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHalted() {
|
public boolean isHalted() {
|
||||||
return halted;
|
return halted;
|
||||||
}
|
}
|
||||||
@ -320,7 +169,7 @@ public class TickerTask implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "TickerTask {\n" + " HALTED = " + halted + "\n" + " tickers = " + tickers + "\n" + " move = " + movingQueue + "\n" + " delete = " + deletionQueue + "\n" + " chunks = " + chunkItemCount + "\n" + " machines = " + machineCount + "\n" + " machinetime = " + machineTimings + "\n" + " chunktime = " + chunkTimings + "\n" + " skipped = " + skippedChunks + "\n" + "}";
|
return "TickerTask {\n" + " HALTED = " + halted + "\n" + " tickers = " + tickers + "\n" + " move = " + movingQueue + "\n" + " delete = " + deletionQueue + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void queueMove(Location from, Location to) {
|
public void queueMove(Location from, Location to) {
|
||||||
|
@ -69,6 +69,10 @@ public final class NumberUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getAsMillis(long nanoseconds) {
|
public static String getAsMillis(long nanoseconds) {
|
||||||
|
if (nanoseconds == 0) {
|
||||||
|
return "0ms";
|
||||||
|
}
|
||||||
|
|
||||||
String number = DECIMAL_FORMAT.format(nanoseconds / 1000000.0);
|
String number = DECIMAL_FORMAT.format(nanoseconds / 1000000.0);
|
||||||
String[] parts = PatternUtils.NUMBER_SEPERATOR.split(number);
|
String[] parts = PatternUtils.NUMBER_SEPERATOR.split(number);
|
||||||
|
|
||||||
@ -76,7 +80,7 @@ public final class NumberUtils {
|
|||||||
return parts[0];
|
return parts[0];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return parts[0] + ',' + ChatColor.GRAY + parts[1] + "ms";
|
return parts[0] + ',' + parts[1] + "ms";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user