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:
Aikar 2020-06-02 23:26:29 -04:00
parent f4a47db699
commit e470f1effe
No known key found for this signature in database
GPG key ID: 401ADFC9891FAAFE
9 changed files with 457 additions and 396 deletions

View file

@ -6,7 +6,7 @@ Subject: [PATCH] Timings v2
diff --git a/src/main/java/co/aikar/timings/FullServerTickHandler.java b/src/main/java/co/aikar/timings/FullServerTickHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..65ff5403cbc894215089626450252a050dd13119
index 0000000000000000000000000000000000000000..dfaa266ff53e43ad48dc5a5a5657fe70600f539a
--- /dev/null
+++ b/src/main/java/co/aikar/timings/FullServerTickHandler.java
@@ -0,0 +1,85 @@
@ -88,7 +88,7 @@ index 0000000000000000000000000000000000000000..65ff5403cbc894215089626450252a05
+ TimingsManager.HISTORY.add(new TimingHistory());
+ TimingsManager.resetTimings();
+ }
+ TimingsExport.reportTimings();
+ Bukkit.getUnsafe().reportTimings();
+ }
+
+ boolean isViolated() {
@ -1255,10 +1255,10 @@ index 0000000000000000000000000000000000000000..df142a89b8c43acb81eb383eac0ef048
+}
diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b34e0d0174b7d92a17699538d5f3fe5196f82b9
index 0000000000000000000000000000000000000000..da76e1aaee1dee794e38ddd4e0a28e0071e90bbf
--- /dev/null
+++ b/src/main/java/co/aikar/timings/Timings.java
@@ -0,0 +1,293 @@
@@ -0,0 +1,296 @@
+/*
+ * This file is licensed under the MIT License (MIT).
+ *
@ -1286,11 +1286,13 @@ index 0000000000000000000000000000000000000000..0b34e0d0174b7d92a17699538d5f3fe5
+
+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..0b34e0d0174b7d92a17699538d5f3fe5
+@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..0b34e0d0174b7d92a17699538d5f3fe5
+ if (sender == null) {
+ sender = Bukkit.getConsoleSender();
+ }
+ TimingsExport.requestingReport.add(sender);
+ requestingReport.add(sender);
+ }
+
+ /**
@ -1519,7 +1522,7 @@ index 0000000000000000000000000000000000000000..0b34e0d0174b7d92a17699538d5f3fe5
+ */
+ public static void generateReport(@NotNull TimingsReportListener sender) {
+ Validate.notNull(sender);
+ TimingsExport.requestingReport.add(sender);
+ requestingReport.add(sender);
+ }
+
+ /*
@ -1554,10 +1557,10 @@ index 0000000000000000000000000000000000000000..0b34e0d0174b7d92a17699538d5f3fe5
+}
diff --git a/src/main/java/co/aikar/timings/TimingsCommand.java b/src/main/java/co/aikar/timings/TimingsCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0d8f2016bbc0257c8eb4888508e8f50d7c4790f
index 0000000000000000000000000000000000000000..f7c2245a310a084367ff25db539b3c967d5cb141
--- /dev/null
+++ b/src/main/java/co/aikar/timings/TimingsCommand.java
@@ -0,0 +1,122 @@
@@ -0,0 +1,119 @@
+/*
+ * This file is licensed under the MIT License (MIT).
+ *
@ -1649,9 +1652,6 @@ index 0000000000000000000000000000000000000000..c0d8f2016bbc0257c8eb4888508e8f50
+ 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..c0d8f2016bbc0257c8eb4888508e8f50
+ 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..93d5a3f97a1b2b3a1cd2731d48e8ebd01d29aa91
--- /dev/null
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -0,0 +1,355 @@
+/*
+ * 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..ef824d701c97cad8b31e76ad98c94fc4367a7eda
@ -3182,10 +2821,18 @@ index f6fb72fab398ae8ca8b746154ff3c8fcad378faf..fad4e929264e2be534d3c4a90a5d557f
* Sends the component to the player
*
diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index 247d194f86c00db11acbc58e7d163b2606db4f07..72c5501e8503aa3b5564a0467fde270d7cd93492 100644
index 247d194f86c00db11acbc58e7d163b2606db4f07..26dbc2a761108d6aad056a786cb5146c63b7ad3d 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -69,4 +69,12 @@ public interface UnsafeValues {
@@ -18,6 +18,7 @@ import org.bukkit.plugin.PluginDescriptionFile;
@Deprecated
public interface UnsafeValues {
+ void reportTimings(); // Paper
Material toLegacy(Material material);
Material fromLegacy(Material material);
@@ -69,4 +70,12 @@ public interface UnsafeValues {
* @return true if a file matching this key was found and deleted
*/
boolean removeAdvancement(NamespacedKey key);

View file

@ -55,10 +55,10 @@ index 0000000000000000000000000000000000000000..2a2651299e8dc631938ba4b4078dc694
+ }
+}
diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index 72c5501e8503aa3b5564a0467fde270d7cd93492..c0ff133dece238297bbdf4cb938c9a3070783ec4 100644
index 26dbc2a761108d6aad056a786cb5146c63b7ad3d..98df287bcb1462fa42b7d4a706a68d36927fbc1c 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -76,5 +76,12 @@ public interface UnsafeValues {
@@ -77,5 +77,12 @@ public interface UnsafeValues {
* @return name
*/
String getTimingsServerName();

View file

@ -7,10 +7,10 @@ Not here to name and shame, only so server admins can be aware of which
plugins have and haven't been updated.
diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index c0ff133dece238297bbdf4cb938c9a3070783ec4..821064264405cde147e944df279b44a20a4868ad 100644
index 98df287bcb1462fa42b7d4a706a68d36927fbc1c..6f5601639c472db62120bab8d903620ee87f7e5c 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -83,5 +83,11 @@ public interface UnsafeValues {
@@ -84,5 +84,11 @@ public interface UnsafeValues {
default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher();
}

View file

@ -6,10 +6,10 @@ Subject: [PATCH] Add Raw Byte ItemStack Serialization
Serializes using NBT which is safer for server data migrations than bukkits format.
diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
index 821064264405cde147e944df279b44a20a4868ad..4fa00140cf17e3ea7d602b6337b3fe4f2901202e 100644
index 6f5601639c472db62120bab8d903620ee87f7e5c..70d95e89a050632b2b0e2f477d3c286a3e8db1bd 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -89,5 +89,9 @@ public interface UnsafeValues {
@@ -90,5 +90,9 @@ public interface UnsafeValues {
static boolean isLegacyPlugin(org.bukkit.plugin.Plugin plugin) {
return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion());
}

View file

@ -6,10 +6,10 @@ Subject: [PATCH] Timings v2
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
new file mode 100644
index 0000000000000000000000000000000000000000..223d3b1125d0781758c45c6b469e6cccd13f187a
index 0000000000000000000000000000000000000000..33342139a042ebec5fb4e8566028f8f7e3149937
--- /dev/null
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -0,0 +1,141 @@
@@ -0,0 +1,145 @@
+package co.aikar.timings;
+
+import com.google.common.collect.MapMaker;
@ -81,9 +81,13 @@ index 0000000000000000000000000000000000000000..223d3b1125d0781758c45c6b469e6ccc
+
+ 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..223d3b1125d0781758c45c6b469e6ccc
+ 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..d4ebcf8f66197299256bd6b65710a1488c90ea41
--- /dev/null
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -0,0 +1,377 @@
+/*
+ * 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..fa1c920ea6092259149f9e7f9cd7cc1ed27bf338
@ -1204,7 +1591,7 @@ index 820180ab3f7053c348caa80cc21f15dfa3d26afd..fa6400dccd4df635d696e0858c0c164a
private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
public CraftPersistentDataContainer persistentDataContainer;
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index d9ff52eda71fcdf88d4c9114450237bcba2ccb08..740c59004514a332ebe556c4e91f84bd9bf4a11d 100644
index 45ded8cd3aee69083f6196765208d8c2430f0a02..a126a6d22ccee90d0aadb8513bda60455a703bf6 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1,5 +1,7 @@
@ -1791,10 +2178,23 @@ index e52ef47b783785dc214746b678e7b549aea9a274..3d90b3426873a3528af14f7f1ab0adae
this.value = value;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 68728b4d86c4037fb1907bd16d86df5e23e8fe77..02f33005335a0995ce4157088353da93833b6ecc 100644
index 68728b4d86c4037fb1907bd16d86df5e23e8fe77..f647450b8c6977b4a6bb1819b34052c4ae2fe0f4 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -298,6 +298,13 @@ public final class CraftMagicNumbers implements UnsafeValues {
@@ -133,6 +133,12 @@ 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);
@@ -298,6 +304,13 @@ public final class CraftMagicNumbers implements UnsafeValues {
return clazz;
}

View file

@ -126,10 +126,10 @@ index 0000000000000000000000000000000000000000..5deed3e25ff41ab0a4015a5fd0c1e952
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 02f33005335a0995ce4157088353da93833b6ecc..b1e1b59d451674b42cdc6f896d3e2b707a03b923 100644
index f647450b8c6977b4a6bb1819b34052c4ae2fe0f4..79ddf03c7f3afc868cb73ee4c000fc879ede90d8 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -303,6 +303,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
@@ -309,6 +309,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
public String getTimingsServerName() {
return com.destroystokyo.paper.PaperConfig.timingsServerName;
}

View file

@ -5,10 +5,10 @@ Subject: [PATCH] Add CraftMagicNumbers.isSupportedApiVersion()
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index b1e1b59d451674b42cdc6f896d3e2b707a03b923..770375ed4207920e71d2d0799c611c4b3cdbe6f7 100644
index 79ddf03c7f3afc868cb73ee4c000fc879ede90d8..6363074c38312387b62e6cf5df2592aefb078f29 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -308,6 +308,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
@@ -314,6 +314,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
return new com.destroystokyo.paper.PaperVersionFetcher();
}

View file

@ -50,10 +50,10 @@ index 2322c0c8c5aacebb6317eab8ce4245554f6d9d55..3e6f878cdbbf5ebfa7fb390745ead135
DataOutputStream dataoutputstream = new DataOutputStream(new BufferedOutputStream(new GZIPOutputStream(outputstream)));
Throwable throwable = null;
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 770375ed4207920e71d2d0799c611c4b3cdbe6f7..b58f2ef5ce5f3047bf8fcfc8d80a4aa9774ff711 100644
index 6363074c38312387b62e6cf5df2592aefb078f29..004b6ce132f8a686116b2a03a0a57461c8e164cf 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -313,6 +313,46 @@ public final class CraftMagicNumbers implements UnsafeValues {
@@ -319,6 +319,46 @@ public final class CraftMagicNumbers implements UnsafeValues {
public boolean isSupportedApiVersion(String apiVersion) {
return apiVersion != null && SUPPORTED_API.contains(apiVersion);
}

View file

@ -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 d4ebcf8f66197299256bd6b65710a1488c90ea41..a3b41ce5fc70948d4804659a472cb622bb9bbc07 100644
--- a/src/main/java/co/aikar/timings/TimingsExport.java
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -153,7 +153,8 @@ 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 4612697569fd6e3683b0e58453b61a9a8d077229..5c8a946d5c895fc2622c7df656cc462c58104cf7 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@ -539,7 +553,7 @@ index 74b868d7cec0260a10ca7718f48308f4ec54d56b..f832e7cdfc6741a932787f02754f1452
}
}
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index 60af90bf8c376ab8ab61b16ae38886149faa88cc..9b726de6daeba42120f3a948fbdcf080d0e72917 100644
index 154b9f9a5696eaebb01dd59b7b27de75e95b501d..3061d856d52776ebc2eed6541238c8a760d7c536 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -151,7 +151,7 @@ public abstract class PlayerList {
@ -570,7 +584,7 @@ index 60af90bf8c376ab8ab61b16ae38886149faa88cc..9b726de6daeba42120f3a948fbdcf080
while (iterator.hasNext()) {
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 4dc03c27bba5cd2dd1d6cb2e82087ef834f41d81..e82b8b9a4dbc9a595df7d8e56596a93fde7b687a 100644
index 9f4268202653f6f0ebed49cd67ae691a8b18ccd2..5535f98ac0ec1668b162cb652e88a122bcadac0c 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -444,8 +444,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable {