mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-29 15:49:00 +01:00
Add more information to Timing Reports
Still needs front end changes to see it yet though. 1) Adds Game Rules per world 2) Adds View distances per world 3) Removes extra garbage on lambda task names 4) Adds more memory information such as native load 5) Adds load average for non crap operating systems. 6) Fixes online mode showing false when privacy=true 7) Adds Data packs loaded
This commit is contained in:
parent
9ad7e2fe15
commit
5201efc608
3 changed files with 431 additions and 370 deletions
|
@ -88,7 +88,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ TimingsManager.HISTORY.add(new TimingHistory());
|
||||
+ TimingsManager.resetTimings();
|
||||
+ }
|
||||
+ TimingsExport.reportTimings();
|
||||
+ Bukkit.getUnsafe().reportTimings();
|
||||
+ }
|
||||
+
|
||||
+ boolean isViolated() {
|
||||
|
@ -1286,11 +1286,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+
|
||||
+import com.google.common.base.Preconditions;
|
||||
+import com.google.common.collect.EvictingQueue;
|
||||
+import com.google.common.collect.Lists;
|
||||
+import org.apache.commons.lang.Validate;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.plugin.Plugin;
|
||||
+
|
||||
+import java.util.List;
|
||||
+import java.util.Queue;
|
||||
+import java.util.logging.Level;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
|
@ -1299,6 +1301,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+@SuppressWarnings({"UnusedDeclaration", "WeakerAccess", "SameParameterValue"})
|
||||
+public final class Timings {
|
||||
+
|
||||
+ final static List<CommandSender> requestingReport = Lists.newArrayList();
|
||||
+ private static final int MAX_HISTORY_FRAMES = 12;
|
||||
+ public static final Timing NULL_HANDLER = new NullTimingHandler();
|
||||
+ static boolean timingsEnabled = false;
|
||||
|
@ -1509,7 +1512,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ if (sender == null) {
|
||||
+ sender = Bukkit.getConsoleSender();
|
||||
+ }
|
||||
+ TimingsExport.requestingReport.add(sender);
|
||||
+ requestingReport.add(sender);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
|
@ -1519,7 +1522,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ */
|
||||
+ public static void generateReport(@NotNull TimingsReportListener sender) {
|
||||
+ Validate.notNull(sender);
|
||||
+ TimingsExport.requestingReport.add(sender);
|
||||
+ requestingReport.add(sender);
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
|
@ -1649,9 +1652,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ lastResetAttempt = now;
|
||||
+ sender.sendMessage(ChatColor.RED + "WARNING: Timings v2 should not be reset. If you are encountering lag, please wait 3 minutes and then issue a report. The best timings will include 10+ minutes, with data before and after your lag period. If you really want to reset, run this command again within 30 seconds.");
|
||||
+ }
|
||||
+
|
||||
+ } else if ("cost".equals(arg)) {
|
||||
+ sender.sendMessage("Timings cost: " + TimingsExport.getCost());
|
||||
+ } else if (
|
||||
+ "paste".equalsIgnoreCase(arg) ||
|
||||
+ "report".equalsIgnoreCase(arg) ||
|
||||
|
@ -1680,367 +1680,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ return ImmutableList.of();
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+/*
|
||||
+ * This file is licensed under the MIT License (MIT).
|
||||
+ *
|
||||
+ * Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||
+ *
|
||||
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
+ * of this software and associated documentation files (the "Software"), to deal
|
||||
+ * in the Software without restriction, including without limitation the rights
|
||||
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
+ * copies of the Software, and to permit persons to whom the Software is
|
||||
+ * furnished to do so, subject to the following conditions:
|
||||
+ *
|
||||
+ * The above copyright notice and this permission notice shall be included in
|
||||
+ * all copies or substantial portions of the Software.
|
||||
+ *
|
||||
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
+ * THE SOFTWARE.
|
||||
+ */
|
||||
+package co.aikar.timings;
|
||||
+
|
||||
+import com.google.common.collect.Lists;
|
||||
+import com.google.common.collect.Sets;
|
||||
+import org.apache.commons.lang.StringUtils;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.ChatColor;
|
||||
+import org.bukkit.Material;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.configuration.ConfigurationSection;
|
||||
+import org.bukkit.configuration.MemorySection;
|
||||
+import org.bukkit.entity.EntityType;
|
||||
+import org.json.simple.JSONObject;
|
||||
+import org.json.simple.JSONValue;
|
||||
+
|
||||
+import java.io.ByteArrayOutputStream;
|
||||
+import java.io.IOException;
|
||||
+import java.io.InputStream;
|
||||
+import java.io.OutputStream;
|
||||
+import java.lang.management.ManagementFactory;
|
||||
+import java.lang.management.RuntimeMXBean;
|
||||
+import java.net.HttpURLConnection;
|
||||
+import java.net.InetAddress;
|
||||
+import java.net.URL;
|
||||
+import java.util.List;
|
||||
+import java.util.Map;
|
||||
+import java.util.Set;
|
||||
+import java.util.logging.Level;
|
||||
+import java.util.zip.GZIPOutputStream;
|
||||
+
|
||||
+import static co.aikar.timings.TimingsManager.HISTORY;
|
||||
+import static co.aikar.util.JSONUtil.appendObjectData;
|
||||
+import static co.aikar.util.JSONUtil.createObject;
|
||||
+import static co.aikar.util.JSONUtil.pair;
|
||||
+import static co.aikar.util.JSONUtil.toArray;
|
||||
+import static co.aikar.util.JSONUtil.toArrayMapper;
|
||||
+import static co.aikar.util.JSONUtil.toObjectMapper;
|
||||
+
|
||||
+@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
|
||||
+class TimingsExport extends Thread {
|
||||
+
|
||||
+ private final TimingsReportListener listeners;
|
||||
+ private final Map out;
|
||||
+ private final TimingHistory[] history;
|
||||
+ private static long lastReport = 0;
|
||||
+ final static List<CommandSender> requestingReport = Lists.newArrayList();
|
||||
+
|
||||
+ private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) {
|
||||
+ super("Timings paste thread");
|
||||
+ this.listeners = listeners;
|
||||
+ this.out = out;
|
||||
+ this.history = history;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Checks if any pending reports are being requested, and builds one if needed.
|
||||
+ */
|
||||
+ static void reportTimings() {
|
||||
+ if (requestingReport.isEmpty()) {
|
||||
+ return;
|
||||
+ }
|
||||
+ TimingsReportListener listeners = new TimingsReportListener(requestingReport);
|
||||
+ listeners.addConsoleIfNeeded();
|
||||
+
|
||||
+ requestingReport.clear();
|
||||
+ long now = System.currentTimeMillis();
|
||||
+ final long lastReportDiff = now - lastReport;
|
||||
+ if (lastReportDiff < 60000) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)");
|
||||
+ listeners.done();
|
||||
+ return;
|
||||
+ }
|
||||
+ final long lastStartDiff = now - TimingsManager.timingStart;
|
||||
+ if (lastStartDiff < 180000) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)");
|
||||
+ listeners.done();
|
||||
+ return;
|
||||
+ }
|
||||
+ listeners.sendMessage(ChatColor.GREEN + "Preparing Timings Report...");
|
||||
+ lastReport = now;
|
||||
+ Map parent = createObject(
|
||||
+ // Get some basic system details about the server
|
||||
+ pair("version", Bukkit.getVersion()),
|
||||
+ pair("maxplayers", Bukkit.getMaxPlayers()),
|
||||
+ pair("start", TimingsManager.timingStart / 1000),
|
||||
+ pair("end", System.currentTimeMillis() / 1000),
|
||||
+ pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000)
|
||||
+ );
|
||||
+ if (!TimingsManager.privacy) {
|
||||
+ appendObjectData(parent,
|
||||
+ pair("server", Bukkit.getUnsafe().getTimingsServerName()),
|
||||
+ pair("motd", Bukkit.getServer().getMotd()),
|
||||
+ pair("online-mode", Bukkit.getServer().getOnlineMode()),
|
||||
+ pair("icon", Bukkit.getServer().getServerIcon().getData())
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ final Runtime runtime = Runtime.getRuntime();
|
||||
+ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
|
||||
+
|
||||
+ parent.put("system", createObject(
|
||||
+ pair("timingcost", getCost()),
|
||||
+ pair("name", System.getProperty("os.name")),
|
||||
+ pair("version", System.getProperty("os.version")),
|
||||
+ pair("jvmversion", System.getProperty("java.version")),
|
||||
+ pair("arch", System.getProperty("os.arch")),
|
||||
+ pair("maxmem", runtime.maxMemory()),
|
||||
+ pair("cpu", runtime.availableProcessors()),
|
||||
+ pair("runtime", ManagementFactory.getRuntimeMXBean().getUptime()),
|
||||
+ pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")),
|
||||
+ pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime()))))
|
||||
+ )
|
||||
+ );
|
||||
+
|
||||
+ Set<Material> tileEntityTypeSet = Sets.newHashSet();
|
||||
+ Set<EntityType> entityTypeSet = Sets.newHashSet();
|
||||
+
|
||||
+ int size = HISTORY.size();
|
||||
+ TimingHistory[] history = new TimingHistory[size + 1];
|
||||
+ int i = 0;
|
||||
+ for (TimingHistory timingHistory : HISTORY) {
|
||||
+ tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet);
|
||||
+ entityTypeSet.addAll(timingHistory.entityTypeSet);
|
||||
+ history[i++] = timingHistory;
|
||||
+ }
|
||||
+
|
||||
+ history[i] = new TimingHistory(); // Current snapshot
|
||||
+ tileEntityTypeSet.addAll(history[i].tileEntityTypeSet);
|
||||
+ entityTypeSet.addAll(history[i].entityTypeSet);
|
||||
+
|
||||
+
|
||||
+ Map handlers = createObject();
|
||||
+ Map groupData;
|
||||
+ synchronized (TimingIdentifier.GROUP_MAP) {
|
||||
+ for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) {
|
||||
+ synchronized (group.handlers) {
|
||||
+ for (TimingHandler id : group.handlers) {
|
||||
+
|
||||
+ if (!id.isTimed() && !id.isSpecial()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ String name = id.identifier.name;
|
||||
+ if (name.startsWith("##")) {
|
||||
+ name = name.substring(3);
|
||||
+ }
|
||||
+ handlers.put(id.id, toArray(
|
||||
+ group.id,
|
||||
+ name
|
||||
+ ));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ groupData = toObjectMapper(
|
||||
+ TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name));
|
||||
+ }
|
||||
+
|
||||
+ parent.put("idmap", createObject(
|
||||
+ pair("groups", groupData),
|
||||
+ pair("handlers", handlers),
|
||||
+ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))),
|
||||
+ pair("tileentity",
|
||||
+ toObjectMapper(tileEntityTypeSet, input -> pair(input.ordinal(), input.name()))),
|
||||
+ pair("entity",
|
||||
+ toObjectMapper(entityTypeSet, input -> pair(input.ordinal(), input.name())))
|
||||
+ ));
|
||||
+
|
||||
+ // Information about loaded plugins
|
||||
+
|
||||
+ parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(),
|
||||
+ plugin -> pair(plugin.getName(), createObject(
|
||||
+ pair("version", plugin.getDescription().getVersion()),
|
||||
+ pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()),
|
||||
+ pair("website", plugin.getDescription().getWebsite()),
|
||||
+ pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", "))
|
||||
+ ))));
|
||||
+
|
||||
+
|
||||
+
|
||||
+ // Information on the users Config
|
||||
+
|
||||
+ parent.put("config", createObject(
|
||||
+ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
|
||||
+ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
|
||||
+ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null))
|
||||
+ ));
|
||||
+
|
||||
+ new TimingsExport(listeners, parent, history).start();
|
||||
+ }
|
||||
+
|
||||
+ static long getCost() {
|
||||
+ // Benchmark the users System.nanotime() for cost basis
|
||||
+ int passes = 100;
|
||||
+ TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1");
|
||||
+ TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2");
|
||||
+ TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3");
|
||||
+ TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4");
|
||||
+ TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5");
|
||||
+ TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6");
|
||||
+
|
||||
+ long start = System.nanoTime();
|
||||
+ for (int i = 0; i < passes; i++) {
|
||||
+ SAMPLER1.startTiming();
|
||||
+ SAMPLER2.startTiming();
|
||||
+ SAMPLER3.startTiming();
|
||||
+ SAMPLER3.stopTiming();
|
||||
+ SAMPLER4.startTiming();
|
||||
+ SAMPLER5.startTiming();
|
||||
+ SAMPLER6.startTiming();
|
||||
+ SAMPLER6.stopTiming();
|
||||
+ SAMPLER5.stopTiming();
|
||||
+ SAMPLER4.stopTiming();
|
||||
+ SAMPLER2.stopTiming();
|
||||
+ SAMPLER1.stopTiming();
|
||||
+ }
|
||||
+ long timingsCost = (System.nanoTime() - start) / passes / 6;
|
||||
+ SAMPLER1.reset(true);
|
||||
+ SAMPLER2.reset(true);
|
||||
+ SAMPLER3.reset(true);
|
||||
+ SAMPLER4.reset(true);
|
||||
+ SAMPLER5.reset(true);
|
||||
+ SAMPLER6.reset(true);
|
||||
+ return timingsCost;
|
||||
+ }
|
||||
+
|
||||
+ private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) {
|
||||
+
|
||||
+ JSONObject object = new JSONObject();
|
||||
+ for (String key : config.getKeys(false)) {
|
||||
+ String fullKey = (parentKey != null ? parentKey + "." + key : key);
|
||||
+ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ final Object val = config.get(key);
|
||||
+
|
||||
+ object.put(key, valAsJSON(val, fullKey));
|
||||
+ }
|
||||
+ return object;
|
||||
+ }
|
||||
+
|
||||
+ private static Object valAsJSON(Object val, final String parentKey) {
|
||||
+ if (!(val instanceof MemorySection)) {
|
||||
+ if (val instanceof List) {
|
||||
+ Iterable<Object> v = (Iterable<Object>) val;
|
||||
+ return toArrayMapper(v, input -> valAsJSON(input, parentKey));
|
||||
+ } else {
|
||||
+ return String.valueOf(val);
|
||||
+ }
|
||||
+ } else {
|
||||
+ return mapAsJSON((ConfigurationSection) val, parentKey);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void run() {
|
||||
+ out.put("data", toArrayMapper(history, TimingHistory::export));
|
||||
+
|
||||
+
|
||||
+ String response = null;
|
||||
+ String timingsURL = null;
|
||||
+ try {
|
||||
+ HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection();
|
||||
+ con.setDoOutput(true);
|
||||
+ String hostName = "BrokenHost";
|
||||
+ try {
|
||||
+ hostName = InetAddress.getLocalHost().getHostName();
|
||||
+ } catch (Exception ignored) {}
|
||||
+ con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getUnsafe().getTimingsServerName() + "/" + hostName);
|
||||
+ con.setRequestMethod("POST");
|
||||
+ con.setInstanceFollowRedirects(false);
|
||||
+
|
||||
+ OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{
|
||||
+ this.def.setLevel(7);
|
||||
+ }};
|
||||
+
|
||||
+ request.write(JSONValue.toJSONString(out).getBytes("UTF-8"));
|
||||
+ request.close();
|
||||
+
|
||||
+ response = getResponse(con);
|
||||
+
|
||||
+ if (con.getResponseCode() != 302) {
|
||||
+ listeners.sendMessage(
|
||||
+ ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage());
|
||||
+ listeners.sendMessage(ChatColor.RED + "Check your logs for more information");
|
||||
+ if (response != null) {
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, response);
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ timingsURL = con.getHeaderField("Location");
|
||||
+ listeners.sendMessage(ChatColor.GREEN + "View Timings Report: " + timingsURL);
|
||||
+
|
||||
+ if (response != null && !response.isEmpty()) {
|
||||
+ Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response);
|
||||
+ }
|
||||
+ } catch (IOException ex) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
|
||||
+ if (response != null) {
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, response);
|
||||
+ }
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex);
|
||||
+ } finally {
|
||||
+ this.listeners.done(timingsURL);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private String getResponse(HttpURLConnection con) throws IOException {
|
||||
+ InputStream is = null;
|
||||
+ try {
|
||||
+ is = con.getInputStream();
|
||||
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
+
|
||||
+ byte[] b = new byte[1024];
|
||||
+ int bytesRead;
|
||||
+ while ((bytesRead = is.read(b)) != -1) {
|
||||
+ bos.write(b, 0, bytesRead);
|
||||
+ }
|
||||
+ return bos.toString();
|
||||
+
|
||||
+ } catch (IOException ex) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
|
||||
+ Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex);
|
||||
+ return null;
|
||||
+ } finally {
|
||||
+ if (is != null) {
|
||||
+ is.close();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/co/aikar/timings/TimingsManager.java b/src/main/java/co/aikar/timings/TimingsManager.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
|
@ -3185,6 +2824,14 @@ diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukk
|
|||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/UnsafeValues.java
|
||||
+++ b/src/main/java/org/bukkit/UnsafeValues.java
|
||||
@@ -0,0 +0,0 @@ import org.bukkit.plugin.PluginDescriptionFile;
|
||||
@Deprecated
|
||||
public interface UnsafeValues {
|
||||
|
||||
+ void reportTimings(); // Paper
|
||||
Material toLegacy(Material material);
|
||||
|
||||
Material fromLegacy(Material material);
|
||||
@@ -0,0 +0,0 @@ public interface UnsafeValues {
|
||||
* @return true if a file matching this key was found and deleted
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,20 @@ Implements world view distance getters/setters
|
|||
Per-Player is absent due to difficulty of maintaining
|
||||
the diff required to make it happen.
|
||||
|
||||
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
@@ -0,0 +0,0 @@ public class TimingsExport extends Thread {
|
||||
pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
|
||||
return pair(rule, world.getWorld().getGameRuleValue(rule));
|
||||
})),
|
||||
- pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance())
|
||||
+ pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()),
|
||||
+ pair("notick-viewdistance", world.getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance())
|
||||
));
|
||||
}));
|
||||
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
|
|
|
@ -81,9 +81,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+
|
||||
+ final String taskname = taskNameCache.computeIfAbsent(taskClass, clazz -> {
|
||||
+ try {
|
||||
+ return clazz.isAnonymousClass() || clazz.isLocalClass()
|
||||
+ ? clazz.getName()
|
||||
+ : clazz.getCanonicalName();
|
||||
+ String clsName = clazz.isAnonymousClass() || clazz.isLocalClass()
|
||||
+ ? clazz.getName()
|
||||
+ : clazz.getCanonicalName();
|
||||
+ if (clsName.contains("$Lambda$")) {
|
||||
+ clsName = clsName.replaceAll("(Lambda\\$.*?)/.*", "$1");
|
||||
+ }
|
||||
+ return clsName;
|
||||
+ } catch (Throwable ex) {
|
||||
+ new Exception("Error occurred detecting class name", ex).printStackTrace();
|
||||
+ return "MangledClassFile";
|
||||
|
@ -151,6 +155,389 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|||
+ return Timings.ofSafe("Command Function - " + function.getMinecraftKey().toString());
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
||||
@@ -0,0 +0,0 @@
|
||||
+/*
|
||||
+ * This file is licensed under the MIT License (MIT).
|
||||
+ *
|
||||
+ * Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||
+ *
|
||||
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
+ * of this software and associated documentation files (the "Software"), to deal
|
||||
+ * in the Software without restriction, including without limitation the rights
|
||||
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
+ * copies of the Software, and to permit persons to whom the Software is
|
||||
+ * furnished to do so, subject to the following conditions:
|
||||
+ *
|
||||
+ * The above copyright notice and this permission notice shall be included in
|
||||
+ * all copies or substantial portions of the Software.
|
||||
+ *
|
||||
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
+ * THE SOFTWARE.
|
||||
+ */
|
||||
+package co.aikar.timings;
|
||||
+
|
||||
+import com.google.common.collect.Sets;
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+import org.apache.commons.lang.StringUtils;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.ChatColor;
|
||||
+import org.bukkit.Material;
|
||||
+import org.bukkit.configuration.ConfigurationSection;
|
||||
+import org.bukkit.configuration.MemorySection;
|
||||
+import org.bukkit.craftbukkit.util.CraftChatMessage;
|
||||
+import org.bukkit.entity.EntityType;
|
||||
+import org.json.simple.JSONObject;
|
||||
+import org.json.simple.JSONValue;
|
||||
+
|
||||
+import java.io.ByteArrayOutputStream;
|
||||
+import java.io.IOException;
|
||||
+import java.io.InputStream;
|
||||
+import java.io.OutputStream;
|
||||
+import java.lang.management.ManagementFactory;
|
||||
+import java.lang.management.OperatingSystemMXBean;
|
||||
+import java.lang.management.RuntimeMXBean;
|
||||
+import java.net.HttpURLConnection;
|
||||
+import java.net.InetAddress;
|
||||
+import java.net.URL;
|
||||
+import java.util.List;
|
||||
+import java.util.Map;
|
||||
+import java.util.Set;
|
||||
+import java.util.logging.Level;
|
||||
+import java.util.zip.GZIPOutputStream;
|
||||
+
|
||||
+import static co.aikar.timings.TimingsManager.HISTORY;
|
||||
+import static co.aikar.util.JSONUtil.appendObjectData;
|
||||
+import static co.aikar.util.JSONUtil.createObject;
|
||||
+import static co.aikar.util.JSONUtil.pair;
|
||||
+import static co.aikar.util.JSONUtil.toArray;
|
||||
+import static co.aikar.util.JSONUtil.toArrayMapper;
|
||||
+import static co.aikar.util.JSONUtil.toObjectMapper;
|
||||
+
|
||||
+@SuppressWarnings({"rawtypes", "SuppressionAnnotation"})
|
||||
+public class TimingsExport extends Thread {
|
||||
+
|
||||
+ private final TimingsReportListener listeners;
|
||||
+ private final Map out;
|
||||
+ private final TimingHistory[] history;
|
||||
+ private static long lastReport = 0;
|
||||
+
|
||||
+ private TimingsExport(TimingsReportListener listeners, Map out, TimingHistory[] history) {
|
||||
+ super("Timings paste thread");
|
||||
+ this.listeners = listeners;
|
||||
+ this.out = out;
|
||||
+ this.history = history;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Checks if any pending reports are being requested, and builds one if needed.
|
||||
+ */
|
||||
+ public static void reportTimings() {
|
||||
+ if (Timings.requestingReport.isEmpty()) {
|
||||
+ return;
|
||||
+ }
|
||||
+ TimingsReportListener listeners = new TimingsReportListener(Timings.requestingReport);
|
||||
+ listeners.addConsoleIfNeeded();
|
||||
+
|
||||
+ Timings.requestingReport.clear();
|
||||
+ long now = System.currentTimeMillis();
|
||||
+ final long lastReportDiff = now - lastReport;
|
||||
+ if (lastReportDiff < 60000) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Please wait at least 1 minute in between Timings reports. (" + (int)((60000 - lastReportDiff) / 1000) + " seconds)");
|
||||
+ listeners.done();
|
||||
+ return;
|
||||
+ }
|
||||
+ final long lastStartDiff = now - TimingsManager.timingStart;
|
||||
+ if (lastStartDiff < 180000) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Please wait at least 3 minutes before generating a Timings report. Unlike Timings v1, v2 benefits from longer timings and is not as useful with short timings. (" + (int)((180000 - lastStartDiff) / 1000) + " seconds)");
|
||||
+ listeners.done();
|
||||
+ return;
|
||||
+ }
|
||||
+ listeners.sendMessage(ChatColor.GREEN + "Preparing Timings Report...");
|
||||
+ lastReport = now;
|
||||
+ Map parent = createObject(
|
||||
+ // Get some basic system details about the server
|
||||
+ pair("version", Bukkit.getVersion()),
|
||||
+ pair("maxplayers", Bukkit.getMaxPlayers()),
|
||||
+ pair("start", TimingsManager.timingStart / 1000),
|
||||
+ pair("end", System.currentTimeMillis() / 1000),
|
||||
+ pair("online-mode", Bukkit.getServer().getOnlineMode()),
|
||||
+ pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000),
|
||||
+ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getResourcePackRepository().d(), pack -> {
|
||||
+ // Don't feel like obf helper'ing these, non fatal if its temp missed.
|
||||
+ return ChatColor.stripColor(CraftChatMessage.fromComponent(pack.a(true)));
|
||||
+ }))
|
||||
+ );
|
||||
+ if (!TimingsManager.privacy) {
|
||||
+ appendObjectData(parent,
|
||||
+ pair("server", Bukkit.getUnsafe().getTimingsServerName()),
|
||||
+ pair("motd", Bukkit.getServer().getMotd()),
|
||||
+ pair("icon", Bukkit.getServer().getServerIcon().getData())
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ final Runtime runtime = Runtime.getRuntime();
|
||||
+ RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
|
||||
+
|
||||
+ OperatingSystemMXBean osInfo = ManagementFactory.getOperatingSystemMXBean();
|
||||
+
|
||||
+ parent.put("system", createObject(
|
||||
+ pair("timingcost", getCost()),
|
||||
+ pair("loadavg", osInfo.getSystemLoadAverage()),
|
||||
+ pair("name", System.getProperty("os.name")),
|
||||
+ pair("version", System.getProperty("os.version")),
|
||||
+ pair("jvmversion", System.getProperty("java.version")),
|
||||
+ pair("arch", System.getProperty("os.arch")),
|
||||
+ pair("maxmem", runtime.maxMemory()),
|
||||
+ pair("memory", createObject(
|
||||
+ pair("heap", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().toString()),
|
||||
+ pair("nonheap", ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().toString()),
|
||||
+ pair("finalizing", ManagementFactory.getMemoryMXBean().getObjectPendingFinalizationCount())
|
||||
+ )),
|
||||
+ pair("cpu", runtime.availableProcessors()),
|
||||
+ pair("runtime", runtimeBean.getUptime()),
|
||||
+ pair("flags", StringUtils.join(runtimeBean.getInputArguments(), " ")),
|
||||
+ pair("gc", toObjectMapper(ManagementFactory.getGarbageCollectorMXBeans(), input -> pair(input.getName(), toArray(input.getCollectionCount(), input.getCollectionTime()))))
|
||||
+ )
|
||||
+ );
|
||||
+
|
||||
+ parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getWorlds(), world -> {
|
||||
+ if (world.getWorldData().getName().equals("worldeditregentempworld")) return null;
|
||||
+ return pair(world.getWorldData().getName(), createObject(
|
||||
+ pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
|
||||
+ return pair(rule, world.getWorld().getGameRuleValue(rule));
|
||||
+ })),
|
||||
+ pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance())
|
||||
+ ));
|
||||
+ }));
|
||||
+
|
||||
+ Set<Material> tileEntityTypeSet = Sets.newHashSet();
|
||||
+ Set<EntityType> entityTypeSet = Sets.newHashSet();
|
||||
+
|
||||
+ int size = HISTORY.size();
|
||||
+ TimingHistory[] history = new TimingHistory[size + 1];
|
||||
+ int i = 0;
|
||||
+ for (TimingHistory timingHistory : HISTORY) {
|
||||
+ tileEntityTypeSet.addAll(timingHistory.tileEntityTypeSet);
|
||||
+ entityTypeSet.addAll(timingHistory.entityTypeSet);
|
||||
+ history[i++] = timingHistory;
|
||||
+ }
|
||||
+
|
||||
+ history[i] = new TimingHistory(); // Current snapshot
|
||||
+ tileEntityTypeSet.addAll(history[i].tileEntityTypeSet);
|
||||
+ entityTypeSet.addAll(history[i].entityTypeSet);
|
||||
+
|
||||
+
|
||||
+ Map handlers = createObject();
|
||||
+ Map groupData;
|
||||
+ synchronized (TimingIdentifier.GROUP_MAP) {
|
||||
+ for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) {
|
||||
+ synchronized (group.handlers) {
|
||||
+ for (TimingHandler id : group.handlers) {
|
||||
+
|
||||
+ if (!id.isTimed() && !id.isSpecial()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ String name = id.identifier.name;
|
||||
+ if (name.startsWith("##")) {
|
||||
+ name = name.substring(3);
|
||||
+ }
|
||||
+ handlers.put(id.id, toArray(
|
||||
+ group.id,
|
||||
+ name
|
||||
+ ));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ groupData = toObjectMapper(
|
||||
+ TimingIdentifier.GROUP_MAP.values(), group -> pair(group.id, group.name));
|
||||
+ }
|
||||
+
|
||||
+ parent.put("idmap", createObject(
|
||||
+ pair("groups", groupData),
|
||||
+ pair("handlers", handlers),
|
||||
+ pair("worlds", toObjectMapper(TimingHistory.worldMap.entrySet(), input -> pair(input.getValue(), input.getKey()))),
|
||||
+ pair("tileentity",
|
||||
+ toObjectMapper(tileEntityTypeSet, input -> pair(input.ordinal(), input.name()))),
|
||||
+ pair("entity",
|
||||
+ toObjectMapper(entityTypeSet, input -> pair(input.ordinal(), input.name())))
|
||||
+ ));
|
||||
+
|
||||
+ // Information about loaded plugins
|
||||
+
|
||||
+ parent.put("plugins", toObjectMapper(Bukkit.getPluginManager().getPlugins(),
|
||||
+ plugin -> pair(plugin.getName(), createObject(
|
||||
+ pair("version", plugin.getDescription().getVersion()),
|
||||
+ pair("description", String.valueOf(plugin.getDescription().getDescription()).trim()),
|
||||
+ pair("website", plugin.getDescription().getWebsite()),
|
||||
+ pair("authors", StringUtils.join(plugin.getDescription().getAuthors(), ", "))
|
||||
+ ))));
|
||||
+
|
||||
+
|
||||
+
|
||||
+ // Information on the users Config
|
||||
+
|
||||
+ parent.put("config", createObject(
|
||||
+ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
|
||||
+ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
|
||||
+ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null))
|
||||
+ ));
|
||||
+
|
||||
+ new TimingsExport(listeners, parent, history).start();
|
||||
+ }
|
||||
+
|
||||
+ static long getCost() {
|
||||
+ // Benchmark the users System.nanotime() for cost basis
|
||||
+ int passes = 100;
|
||||
+ TimingHandler SAMPLER1 = Timings.ofSafe("Timings Sampler 1");
|
||||
+ TimingHandler SAMPLER2 = Timings.ofSafe("Timings Sampler 2");
|
||||
+ TimingHandler SAMPLER3 = Timings.ofSafe("Timings Sampler 3");
|
||||
+ TimingHandler SAMPLER4 = Timings.ofSafe("Timings Sampler 4");
|
||||
+ TimingHandler SAMPLER5 = Timings.ofSafe("Timings Sampler 5");
|
||||
+ TimingHandler SAMPLER6 = Timings.ofSafe("Timings Sampler 6");
|
||||
+
|
||||
+ long start = System.nanoTime();
|
||||
+ for (int i = 0; i < passes; i++) {
|
||||
+ SAMPLER1.startTiming();
|
||||
+ SAMPLER2.startTiming();
|
||||
+ SAMPLER3.startTiming();
|
||||
+ SAMPLER3.stopTiming();
|
||||
+ SAMPLER4.startTiming();
|
||||
+ SAMPLER5.startTiming();
|
||||
+ SAMPLER6.startTiming();
|
||||
+ SAMPLER6.stopTiming();
|
||||
+ SAMPLER5.stopTiming();
|
||||
+ SAMPLER4.stopTiming();
|
||||
+ SAMPLER2.stopTiming();
|
||||
+ SAMPLER1.stopTiming();
|
||||
+ }
|
||||
+ long timingsCost = (System.nanoTime() - start) / passes / 6;
|
||||
+ SAMPLER1.reset(true);
|
||||
+ SAMPLER2.reset(true);
|
||||
+ SAMPLER3.reset(true);
|
||||
+ SAMPLER4.reset(true);
|
||||
+ SAMPLER5.reset(true);
|
||||
+ SAMPLER6.reset(true);
|
||||
+ return timingsCost;
|
||||
+ }
|
||||
+
|
||||
+ private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) {
|
||||
+
|
||||
+ JSONObject object = new JSONObject();
|
||||
+ for (String key : config.getKeys(false)) {
|
||||
+ String fullKey = (parentKey != null ? parentKey + "." + key : key);
|
||||
+ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ final Object val = config.get(key);
|
||||
+
|
||||
+ object.put(key, valAsJSON(val, fullKey));
|
||||
+ }
|
||||
+ return object;
|
||||
+ }
|
||||
+
|
||||
+ private static Object valAsJSON(Object val, final String parentKey) {
|
||||
+ if (!(val instanceof MemorySection)) {
|
||||
+ if (val instanceof List) {
|
||||
+ Iterable<Object> v = (Iterable<Object>) val;
|
||||
+ return toArrayMapper(v, input -> valAsJSON(input, parentKey));
|
||||
+ } else {
|
||||
+ return String.valueOf(val);
|
||||
+ }
|
||||
+ } else {
|
||||
+ return mapAsJSON((ConfigurationSection) val, parentKey);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void run() {
|
||||
+ out.put("data", toArrayMapper(history, TimingHistory::export));
|
||||
+
|
||||
+
|
||||
+ String response = null;
|
||||
+ String timingsURL = null;
|
||||
+ try {
|
||||
+ HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection();
|
||||
+ con.setDoOutput(true);
|
||||
+ String hostName = "BrokenHost";
|
||||
+ try {
|
||||
+ hostName = InetAddress.getLocalHost().getHostName();
|
||||
+ } catch (Exception ignored) {}
|
||||
+ con.setRequestProperty("User-Agent", "Paper/" + Bukkit.getUnsafe().getTimingsServerName() + "/" + hostName);
|
||||
+ con.setRequestMethod("POST");
|
||||
+ con.setInstanceFollowRedirects(false);
|
||||
+
|
||||
+ OutputStream request = new GZIPOutputStream(con.getOutputStream()) {{
|
||||
+ this.def.setLevel(7);
|
||||
+ }};
|
||||
+
|
||||
+ request.write(JSONValue.toJSONString(out).getBytes("UTF-8"));
|
||||
+ request.close();
|
||||
+
|
||||
+ response = getResponse(con);
|
||||
+
|
||||
+ if (con.getResponseCode() != 302) {
|
||||
+ listeners.sendMessage(
|
||||
+ ChatColor.RED + "Upload Error: " + con.getResponseCode() + ": " + con.getResponseMessage());
|
||||
+ listeners.sendMessage(ChatColor.RED + "Check your logs for more information");
|
||||
+ if (response != null) {
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, response);
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ timingsURL = con.getHeaderField("Location");
|
||||
+ listeners.sendMessage(ChatColor.GREEN + "View Timings Report: " + timingsURL);
|
||||
+
|
||||
+ if (response != null && !response.isEmpty()) {
|
||||
+ Bukkit.getLogger().log(Level.INFO, "Timing Response: " + response);
|
||||
+ }
|
||||
+ } catch (IOException ex) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
|
||||
+ if (response != null) {
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, response);
|
||||
+ }
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, "Could not paste timings", ex);
|
||||
+ } finally {
|
||||
+ this.listeners.done(timingsURL);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private String getResponse(HttpURLConnection con) throws IOException {
|
||||
+ InputStream is = null;
|
||||
+ try {
|
||||
+ is = con.getInputStream();
|
||||
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
+
|
||||
+ byte[] b = new byte[1024];
|
||||
+ int bytesRead;
|
||||
+ while ((bytesRead = is.read(b)) != -1) {
|
||||
+ bos.write(b, 0, bytesRead);
|
||||
+ }
|
||||
+ return bos.toString();
|
||||
+
|
||||
+ } catch (IOException ex) {
|
||||
+ listeners.sendMessage(ChatColor.RED + "Error uploading timings, check your logs for more information");
|
||||
+ Bukkit.getLogger().log(Level.WARNING, con.getResponseMessage(), ex);
|
||||
+ return null;
|
||||
+ } finally {
|
||||
+ if (is != null) {
|
||||
+ is.close();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
||||
|
@ -1794,6 +2181,19 @@ diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/
|
|||
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
||||
@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
||||
return CraftNamespacedKey.toMinecraft(mat.getKey());
|
||||
}
|
||||
// ========================================================================
|
||||
+ // Paper start
|
||||
+ @Override
|
||||
+ public void reportTimings() {
|
||||
+ co.aikar.timings.TimingsExport.reportTimings();
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
public static byte toLegacyData(IBlockData data) {
|
||||
return CraftLegacy.toLegacyData(data);
|
||||
@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
||||
return clazz;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue