Skip to content

Commit dd00b0e

Browse files
authored
Milliseconds per Tick (#4243)
* Spigot MSPT method * Implement MSPT for Paper, Sponge & Fabric * Add average mspt and 95th percentile mspt to TPS object * Add average and 95th percentile MSPT to database * Update checkstyle to ignore ClassFanOutComplexity for Patches.java Previously these were in SQLDB.java, which was also ignored, no longer is. * Improve gathering accuracy by using direct data rather than aggregating averages * Fix TPSQueries typo * Add language for MSPT and a couple of small fixes * Correct mspt access - Support raw net.minecraft.server package in reflection - The field contains nanos instead of millis, format accordingly. * Add mspt to server graphs * Add MSPT to performance as numbers, insights and network performance * Increase all performance graph mspt softmax to 50 * Fix label * Add mspt 95th percentile to Network performance * Add functions to update themes with missing use cases and mspt colors to themes * Remove outdated comment * Map nanos to millis earlier so that overflows are avoided * Add MSPT specific performance graph permission * Fix sponge and fabric MSPT gathering * Fix spigot MSPT field access Affects issues: - Close #2279
1 parent 97216d3 commit dd00b0e

81 files changed

Lines changed: 977 additions & 219 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Plan/bukkit/src/main/java/com/djrapitops/plan/BukkitServerShutdownSave.java

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.djrapitops.plan.utilities.java.Reflection;
2525
import com.djrapitops.plan.utilities.logging.ErrorLogger;
2626
import net.playeranalytics.plugin.server.PluginLogger;
27-
import org.bukkit.Bukkit;
2827

2928
import javax.inject.Inject;
3029
import javax.inject.Singleton;
@@ -52,7 +51,7 @@ public BukkitServerShutdownSave(
5251

5352
@Override
5453
protected boolean checkServerShuttingDownStatus() {
55-
return isStoppedBefore1p17() || isStoppedV1p17() || isStoppedAfterV1p17();
54+
return isStoppedBefore1p17() || isStoppedAfterV1p17();
5655
}
5756

5857
@Override
@@ -65,37 +64,18 @@ private boolean isStoppedBefore1p17() {
6564
// Special thanks to Fuzzlemann for figuring out the methods required for this check.
6665
// https://github.com/plan-player-analytics/Plan/issues/769#issuecomment-433898242
6766
Class<?> minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer");
68-
Object minecraftServer = Reflection.getField(minecraftServerClass, "SERVER", minecraftServerClass).get(null);
69-
70-
return Reflection.getField(minecraftServerClass, IS_STOPPED, boolean.class).get(minecraftServer);
67+
return Reflection.getField(minecraftServerClass, IS_STOPPED, boolean.class).get(Reflection.getMinecraftServer().orElse(null));
7168
} catch (Exception | NoClassDefFoundError | NoSuchFieldError e) {
7269
return false;
7370
}
7471
}
7572

76-
private boolean isStoppedV1p17() {
77-
try {
78-
// Special thanks to Fuzzlemann for figuring out the methods required for this check.
79-
// https://github.com/plan-player-analytics/Plan/issues/769#issuecomment-433898242
80-
Class<?> minecraftServerClass = Class.forName("net.minecraft.server.MinecraftServer");
81-
Class<?> craftServerClass = Reflection.getCraftBukkitClass("CraftServer");
82-
Object minecraftServer = Reflection.getField(craftServerClass, "console", minecraftServerClass).get(Bukkit.getServer());
83-
84-
return (Boolean) minecraftServerClass.getMethod(IS_STOPPED).invoke(minecraftServer);
85-
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) {
86-
return false;
87-
}
88-
}
89-
9073
private boolean isStoppedAfterV1p17() {
9174
try {
9275
// Special thanks to Fuzzlemann for figuring out the methods required for this check.
9376
// https://github.com/plan-player-analytics/Plan/issues/769#issuecomment-433898242
9477
Class<?> minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer");
95-
Class<?> craftServerClass = Reflection.getCraftBukkitClass("CraftServer");
96-
Object minecraftServer = Reflection.getField(craftServerClass, "console", minecraftServerClass).get(Bukkit.getServer());
97-
98-
return (Boolean) minecraftServerClass.getMethod(IS_STOPPED).invoke(minecraftServer);
78+
return (Boolean) minecraftServerClass.getMethod(IS_STOPPED).invoke(Reflection.getMinecraftServer().orElse(null));
9979
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) {
10080
return false;
10181
}

Plan/bukkit/src/main/java/com/djrapitops/plan/gathering/BukkitSensor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.djrapitops.plan.gathering;
1818

1919
import com.djrapitops.plan.gathering.domain.PluginMetadata;
20+
import com.djrapitops.plan.gathering.timed.mspt.SpigotMspt;
2021
import org.bukkit.Bukkit;
2122
import org.bukkit.OfflinePlayer;
2223
import org.bukkit.Server;
@@ -28,6 +29,7 @@
2829
import javax.inject.Singleton;
2930
import java.util.Arrays;
3031
import java.util.List;
32+
import java.util.Optional;
3133
import java.util.UUID;
3234
import java.util.stream.Collectors;
3335

@@ -155,4 +157,8 @@ public boolean isBanned(UUID playerUUID) {
155157
return player.isBanned();
156158
}
157159

160+
@Override
161+
public Optional<long[]> getMspt() {
162+
return SpigotMspt.getMspt();
163+
}
158164
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.gathering.timed.mspt;
18+
19+
import com.djrapitops.plan.utilities.java.Reflection;
20+
21+
import java.util.Optional;
22+
23+
/**
24+
* Accessor for the average MSPT field in MinecraftServer class which is a long[] array with 100 values.
25+
*
26+
* @author AuroraLS3
27+
*/
28+
public class SpigotMspt {
29+
30+
public static Optional<long[]> getMspt() {
31+
return Optional.ofNullable(getValue());
32+
}
33+
34+
private static long[] getValue() {
35+
try {
36+
Class<?> minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer");
37+
Reflection.FieldAccessor<long[]> field = Reflection.findField(minecraftServerClass, long[].class);
38+
return field.get(Reflection.getMinecraftServer().orElse(null));
39+
} catch (Exception | NoClassDefFoundError | NoSuchFieldError e) {
40+
return null;
41+
}
42+
}
43+
44+
}

Plan/bukkit/src/main/java/com/djrapitops/plan/utilities/java/Reflection.java

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.bukkit.Server;
2828

2929
import java.lang.reflect.Field;
30+
import java.util.Optional;
3031

3132
/**
3233
* An utility class that simplifies reflection in Bukkit plugins.
@@ -46,6 +47,35 @@ private Reflection() {
4647
// Seal class
4748
}
4849

50+
public static Optional<Object> getMinecraftServer() {
51+
Optional<Object> minecraftServerBeforeV1p17 = getMinecraftServerBeforeV1p17();
52+
if (minecraftServerBeforeV1p17.isPresent()) {return minecraftServerBeforeV1p17;}
53+
return getMinecraftServerAfterV1p17();
54+
}
55+
56+
private static Optional<Object> getMinecraftServerBeforeV1p17() {
57+
try {
58+
Class<?> minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer");
59+
Object minecraftServer = Reflection.getField(minecraftServerClass, "SERVER", minecraftServerClass).get(null);
60+
61+
return Optional.ofNullable(minecraftServer);
62+
} catch (Exception | NoClassDefFoundError | NoSuchFieldError e) {
63+
return Optional.empty();
64+
}
65+
}
66+
67+
private static Optional<Object> getMinecraftServerAfterV1p17() {
68+
try {
69+
Class<?> minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer");
70+
Class<?> craftServerClass = Reflection.getCraftBukkitClass("CraftServer");
71+
Object minecraftServer = Reflection.getField(craftServerClass, "console", minecraftServerClass).get(Bukkit.getServer());
72+
73+
return Optional.ofNullable(minecraftServer);
74+
} catch (Exception | NoClassDefFoundError | NoSuchFieldError e) {
75+
return Optional.empty();
76+
}
77+
}
78+
4979
private static String getOBCPrefix() {
5080
Server server = Bukkit.getServer();
5181
return server != null ? server.getClass().getPackage().getName() : Server.class.getPackage().getName();
@@ -109,6 +139,46 @@ public boolean hasField(Object target) {
109139
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
110140
}
111141

142+
public static <T> FieldAccessor<T> findField(Class<?> target, Class<T> fieldType) {
143+
for (final Field field : target.getDeclaredFields()) {
144+
if (fieldType.isAssignableFrom(field.getType())) {
145+
146+
return new FieldAccessor<>() {
147+
148+
@Override
149+
@SuppressWarnings("unchecked")
150+
public T get(Object target) {
151+
try {
152+
if (!field.canAccess(target)) {
153+
field.setAccessible(true);
154+
}
155+
return (T) field.get(target);
156+
} catch (IllegalAccessException e) {
157+
throw new IllegalStateException("Cannot access reflection.", e);
158+
}
159+
}
160+
161+
@Override
162+
public void set(Object target, Object value) {
163+
try {
164+
field.set(target, value);
165+
} catch (IllegalAccessException e) {
166+
throw new IllegalStateException("Cannot access reflection.", e);
167+
}
168+
}
169+
170+
@Override
171+
public boolean hasField(Object target) {
172+
// target instanceof DeclaringClass
173+
return field.getDeclaringClass().isAssignableFrom(target.getClass());
174+
}
175+
};
176+
}
177+
}
178+
179+
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
180+
}
181+
112182
/**
113183
* Retrieve a class in the net.minecraft.server.VERSION.* package.
114184
*
@@ -117,7 +187,16 @@ public boolean hasField(Object target) {
117187
* @throws IllegalArgumentException If the class doesn't exist.
118188
*/
119189
public static Class<?> getMinecraftClass(String name) {
120-
return getCanonicalClass(NMS_PREFIX + '.' + name);
190+
try {
191+
return getCanonicalClass(NMS_PREFIX + '.' + name);
192+
} catch (IllegalArgumentException suppressed) {
193+
try {
194+
return getCanonicalClass("net.minecraft.server." + name);
195+
} catch (IllegalArgumentException e) {
196+
e.addSuppressed(suppressed);
197+
throw e;
198+
}
199+
}
121200
}
122201

123202
/**

Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public enum WebPermission implements Supplier<String>, Lang {
103103
PAGE_SERVER_PERFORMANCE_GRAPHS_CHUNKS("See Chunk count data in Performance graphs"),
104104
PAGE_SERVER_PERFORMANCE_GRAPHS_DISK("See Disk Space usage Performance graphs"),
105105
PAGE_SERVER_PERFORMANCE_GRAPHS_PING("See Ping data in Performance graphs"),
106+
PAGE_SERVER_PERFORMANCE_GRAPHS_MSPT("See MSPT data in Performance graphs"),
106107
PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"),
107108
PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"),
108109
PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"),

Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/TPSMutator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraph;
2020
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
2121
import com.djrapitops.plan.gathering.domain.TPS;
22+
import com.djrapitops.plan.utilities.analysis.Average;
2223
import com.djrapitops.plan.utilities.comparators.TPSComparator;
2324
import com.djrapitops.plan.utilities.java.Lists;
2425

@@ -300,4 +301,12 @@ private void addMissingPoints(List<Number[]> arrays, Long lastX, long date, Line
300301
iterate += gapStrategy.fillFrequencyMs;
301302
}
302303
}
304+
305+
public double averageMspt() {
306+
Average average = new Average();
307+
for (TPS tps : tpsData) {
308+
average.addNonNull(tps.getMsptAverage());
309+
}
310+
return average.getAverageAndReset();
311+
}
303312
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PerformanceJSONCreator.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ private Map<String, Object> createNumbersMap(List<TPS> tpsData) {
128128
numbers.put("min_disk_7d", formatBytes(tpsDataWeek.minFreeDisk()));
129129
numbers.put("min_disk_24h", formatBytes(tpsDataDay.minFreeDisk()));
130130

131+
numbers.put("mspt_average_30d", format(tpsDataMonth.averageMspt()));
132+
numbers.put("mspt_average_7d", format(tpsDataWeek.averageMspt()));
133+
numbers.put("mspt_average_24h", format(tpsDataDay.averageMspt()));
134+
131135
return numbers;
132136
}
133137

@@ -155,11 +159,13 @@ private Map<String, Object> createInsightsMap(List<TPS> tpsData) {
155159
double averageCPU = lowTPS.averageCPU();
156160
double averageEntities = lowTPS.averageEntities();
157161
double averageChunks = lowTPS.averageChunks();
162+
double averageMspt = lowTPS.averageMspt();
158163
insights.put("low_tps_players", avgPlayersOnline != -1 ? decimals.apply(avgPlayersOnline) : HtmlLang.TEXT_NO_LOW_TPS.getKey());
159164
insights.put("low_tps_tps", averageTPS != -1 ? decimals.apply(averageTPS) : "-");
160165
insights.put("low_tps_cpu", averageCPU != -1 ? decimals.apply(averageCPU) : "-");
161166
insights.put("low_tps_entities", averageEntities != -1 ? decimals.apply(averageEntities) : "-");
162167
insights.put("low_tps_chunks", averageChunks != -1 ? decimals.apply(averageChunks) : "-");
168+
insights.put("low_tps_mspt", averageMspt > 0 ? decimals.apply(averageMspt) : "-");
163169

164170
return insights;
165171
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/graphs/GraphJSONCreator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public Map<String, Object> optimizedPerformanceGraphJSON(ServerUUID serverUUID)
162162
)));
163163

164164
return Maps.builder(String.class, Object.class)
165-
.put("keys", new String[]{"date", "playersOnline", "tps", "cpu", "ram", "entities", "chunks", "disk"})
165+
.put("keys", new String[]{"date", "playersOnline", "tps", "cpu", "ram", "entities", "chunks", "disk", "msptAverage", "mspt95thPercentile"})
166166
.put("values", values)
167167
.put("colors", Maps.builder(String.class, Object.class)
168168
.put("playersOnline", ThemeVal.GRAPH_PLAYERS_ONLINE.getDefaultValue())

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ public Response themeResponse(@Untrusted String themeName, Request request) {
558558
String resourceName = themeName + ".json";
559559
WebResource foundTheme = files.attemptToFind(themeDirectory, resourceName)
560560
.map(file -> (Resource) new FileResource(file.getName(), file))
561+
.filter(file -> request.getQuery().get("jarOnly").isEmpty())
561562
.orElseGet(() -> files.getResourceFromJar("themes/" + themeName + ".json"))
562563
.asWebResource();
563564

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/NetworkPerformanceJSONResolver.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ public class NetworkPerformanceJSONResolver implements Resolver {
6666
private final DBSystem dbSystem;
6767

6868
private final Formatter<Double> decimals;
69-
private final Formatter<Long> timeAmount;
7069
private final Formatter<Double> percentage;
7170
private final Formatter<Double> byteSize;
7271
private final Gson gson;
@@ -83,7 +82,6 @@ public NetworkPerformanceJSONResolver(
8382

8483
decimals = formatters.decimals();
8584
percentage = formatters.percentage();
86-
timeAmount = formatters.timeAmount();
8785
byteSize = formatters.byteSize();
8886
this.gson = gson;
8987
}
@@ -213,6 +211,9 @@ private Map<String, Object> createNumbersMap(Map<Integer, List<TPS>> tpsData) {
213211
numbers.put("chunks_30d", format((int) tpsDataMonth.averageChunks()));
214212
numbers.put("chunks_7d", format((int) tpsDataWeek.averageChunks()));
215213
numbers.put("chunks_24h", format((int) tpsDataDay.averageChunks()));
214+
numbers.put("mspt_average_30d", format(tpsDataMonth.averageMspt()));
215+
numbers.put("mspt_average_7d", format(tpsDataWeek.averageMspt()));
216+
numbers.put("mspt_average_24h", format(tpsDataDay.averageMspt()));
216217

217218
return numbers;
218219
}

0 commit comments

Comments
 (0)