1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach.brown@destroystokyo.com>
Date: Fri, 24 Mar 2017 23:56:01 -0500
Subject: [PATCH] Paper Metrics
Removes Spigot's mcstats metrics in favor of a system using bStats
To disable for privacy or other reasons go to the bStats folder in your plugins folder
and edit the config.yml file present there.
Please keep in mind the data collected is anonymous and collection should have no
tangible effect on server performance. The data is used to allow the authors of
PaperMC to track version and platform usage so that we can make better management
decisions on behalf of the project.
diff --git a/build.gradle.kts b/build.gradle.kts
index 4db0cc3f8505747e77d314320545eb71904b4eac..6baabc30a363d132ea3d8a7da54a40aaf918f15b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -16,6 +16,7 @@ dependencies {
implementation("org.apache.logging.log4j:log4j-iostreams:2.14.1") // Paper
implementation("org.ow2.asm:asm:9.2")
implementation("org.ow2.asm:asm-commons:9.2") // Paper - ASM event executor generation
+ implementation("org.bstats:bstats-base:2.2.1") // Paper
runtimeOnly("org.xerial:sqlite-jdbc:3.36.0.3")
runtimeOnly("mysql:mysql-connector-java:8.0.27")
@@ -65,6 +66,7 @@ relocation {
relocate("org.bukkit.craftbukkit" to "org.bukkit.craftbukkit.v$packageVersion") {
exclude("org.bukkit.craftbukkit.Main*")
}
+ relocate("org.bstats:bstats-base" to "org.bstats") // Paper
}
tasks.shadowJar {
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
new file mode 100644
index 0000000000000000000000000000000000000000..6402516b8b1a04489184dc2adde83d6cbc5e83d5
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
@@ -0,0 +1,236 @@
+package com.destroystokyo.paper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.minecraft.server.MinecraftServer;
+import org.bstats.MetricsBase;
+import org.bstats.charts.DrilldownPie;
+import org.bstats.charts.SimplePie;
+import org.bstats.charts.SingleLineChart;
+import org.bstats.json.JsonObjectBuilder;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
+import org.bukkit.plugin.Plugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * bStats collects some data for plugin authors.
+ *
+ * Check out https://bStats.org/ to learn more about bStats!
+ */
+public class Metrics {
+
+ private static Logger logger = LoggerFactory.getLogger("bStats");
+
+ public Metrics() {
+ PaperMetricsConfig config = new PaperMetricsConfig();
+ MetricsBase metricsBase = new MetricsBase(
+ "server-implementation",
+ config.getServerUUID(),
+ 580,
+ config.isEnabled(),
+ this::appendPlatformData,
+ jsonObjectBuilder -> { /* NOP */ },
+ null,
+ () -> !MinecraftServer.getServer().hasStopped(),
+ logger::warn,
+ logger::info,
+ config.isLogErrorsEnabled(),
+ config.isLogSentDataEnabled(),
+ config.isLogResponseStatusTextEnabled()
+ );
+
+ metricsBase.addCustomChart(new SimplePie("minecraft_version", () -> {
+ String minecraftVersion = Bukkit.getVersion();
+ minecraftVersion = minecraftVersion.substring(minecraftVersion.indexOf("MC: ") + 4, minecraftVersion.length() - 1);
+ return minecraftVersion;
+ }));
+
+ metricsBase.addCustomChart(new SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
+ metricsBase.addCustomChart(new SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline"));
+ metricsBase.addCustomChart(new SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"));
+
+ metricsBase.addCustomChart(new DrilldownPie("java_version", () -> {
+ Map<String, Map<String, Integer>> map = new HashMap<>();
+ String javaVersion = System.getProperty("java.version");
+ Map<String, Integer> entry = new HashMap<>();
+ entry.put(javaVersion, 1);
+
+ // http://openjdk.java.net/jeps/223
+ // Java decided to change their versioning scheme and in doing so modified the java.version system
+ // property to return $major[.$minor][.$secuity][-ea], as opposed to 1.$major.0_$identifier
+ // we can handle pre-9 by checking if the "major" is equal to "1", otherwise, 9+
+ String majorVersion = javaVersion.split("\\.")[0];
+ String release;
+
+ int indexOf = javaVersion.lastIndexOf('.');
+
+ if(majorVersion.equals("1")) {
+ release = "Java " + javaVersion.substring(0, indexOf);
+ } else {
+ // of course, it really wouldn't be all that simple if they didn't add a quirk, now would it
+ // valid strings for the major may potentially include values such as -ea to deannotate a pre release
+ Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
+ if(versionMatcher.find()) {
+ majorVersion = versionMatcher.group(0);
+ }
+ release = "Java " + majorVersion;
+ }
+ map.put(release, entry);
+
+ return map;
+ }));
+
+ metricsBase.addCustomChart(new DrilldownPie("legacy_plugins", () -> {
+ Map<String, Map<String, Integer>> map = new HashMap<>();
+
+ // count legacy plugins
+ int legacy = 0;
+ for(Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
+ if(CraftMagicNumbers.isLegacy(plugin.getDescription())) {
+ legacy++;
+ }
+ }
+
+ // insert real value as lower dimension
+ Map<String, Integer> entry = new HashMap<>();
+ entry.put(String.valueOf(legacy), 1);
+
+ // create buckets as higher dimension
+ if(legacy == 0) {
+ map.put("0 \uD83D\uDE0E", entry); // :sunglasses:
+ } else if(legacy <= 5) {
+ map.put("1-5", entry);
+ } else if(legacy <= 10) {
+ map.put("6-10", entry);
+ } else if(legacy <= 25) {
+ map.put("11-25", entry);
+ } else if(legacy <= 50) {
+ map.put("26-50", entry);
+ } else {
+ map.put("50+ \uD83D\uDE2D", entry); // :cry:
+ }
+
+ return map;
+ }));
+
+ metricsBase.addCustomChart(new DrilldownPie("plugins", () -> {
+ Map<String, Map<String, Integer>> map = new HashMap<>();
+
+ int count = Bukkit.getPluginManager().getPlugins().length;
+
+ // insert real value as lower dimension
+ Map<String, Integer> entry = new HashMap<>();
+ entry.put(String.valueOf(count), 1);
+
+ // create buckets as higher dimension
+ if(count == 0) {
+ map.put("0", entry);
+ } else if(count <= 5) {
+ map.put("1-5", entry);
+ } else if(count <= 10) {
+ map.put("6-10", entry);
+ } else if(count <= 25) {
+ map.put("11-25", entry);
+ } else if(count <= 50) {
+ map.put("26-50", entry);
+ } else {
+ map.put("50+", entry);
+ }
+
+ return map;
+ }));
+ }
+
+ private void appendPlatformData(JsonObjectBuilder builder) {
+ builder.appendField("osName", System.getProperty("os.name"));
+ builder.appendField("osArch", System.getProperty("os.arch"));
+ builder.appendField("osVersion", System.getProperty("os.version"));
+ builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
+ }
+
+ static class PaperMetricsConfig {
+
+ private String serverUUID;
+ private boolean enabled;
+ private boolean logErrors;
+ private boolean logSentData;
+ private boolean logResponseStatusText;
+
+ public PaperMetricsConfig() {
+ setupConfig();
+ }
+
+ private void setupConfig() {
+ File bStatsFolder = new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "bStats");
+ File configFile = new File(bStatsFolder, "config.yml");
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
+
+ if(!config.isSet("serverUuid")) {
+ config.addDefault("enabled", true);
+ config.addDefault("serverUuid", UUID.randomUUID().toString());
+ config.addDefault("logFailedRequests", false);
+ config.addDefault("logSentData", false);
+ config.addDefault("logResponseStatusText", false);
+
+ // Inform the server owners about bStats
+ config.options().header(
+ "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" +
+ "many people use their plugin and their total player count. It's recommended to keep bStats\n" +
+ "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" +
+ "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" +
+ "anonymous."
+ ).copyDefaults(true);
+ try {
+ config.save(configFile);
+ } catch(IOException ignored) {
+ }
+
+ // Send an info message when the bStats config file gets created for the first time
+ logger.info("Paper and some of its plugins collect metrics"
+ + " and send them to bStats (https://bStats.org).");
+ logger.info("bStats collects some basic information for plugin"
+ + " authors, like how many people use");
+ logger.info("their plugin and their total player count."
+ + " It's recommended to keep bStats enabled, but");
+ logger.info("if you're not comfortable with this, you can opt-out"
+ + " by editing the config.yml file in");
+ logger.info("the '{}' folder and setting enabled to false.", bStatsFolder.getPath());
+ }
+
+ // Load the data
+ this.enabled = config.getBoolean("enabled", true);
+ this.serverUUID = config.getString("serverUuid");
+ this.logErrors = config.getBoolean("logFailedRequests", false);
+ this.logSentData = config.getBoolean("logSentData", false);
+ this.logResponseStatusText = config.getBoolean("logResponseStatusText", false);
+ }
+
+ public String getServerUUID() {
+ return this.serverUUID;
+ }
+
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ public boolean isLogErrorsEnabled() {
+ return this.logErrors;
+ }
+
+ public boolean isLogSentDataEnabled() {
+ return this.logSentData;
+ }
+
+ public boolean isLogResponseStatusTextEnabled() {
+ return this.logResponseStatusText;
+ }
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index e4368db074da7b5e48b47d41875c1e63b9745c2a..f86d64b8711b4a8ef7e666fca1c88acbfadb41e8 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -42,6 +42,7 @@ public class PaperConfig {
private static boolean verbose;
private static boolean fatalError;
/*========================================================================*/
+ private static boolean metricsStarted;
public static void init(File configFile) {
CONFIG_FILE = configFile;
@@ -84,6 +85,11 @@ public class PaperConfig {
for (Map.Entry<String, Command> entry : commands.entrySet()) {
MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue());
}
+
+ if (!metricsStarted) {
+ new Metrics();
+ metricsStarted = true;
+ }
}
static void readConfig(Class<?> clazz, Object instance) {
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
index c90db55aadb1d16f6cc4e02e57a13a3c3fe6a420..5a912528f82e8f97229a412b0bf72e04a520b556 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -83,6 +83,7 @@ public class SpigotConfig
MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() );
}
+ /* // Paper - Replace with our own
if ( SpigotConfig.metrics == null )
{
try
@@ -94,6 +95,7 @@ public class SpigotConfig
Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex );
}
}
+ */ // Paper end
}
static void readConfig(Class<?> clazz, Object instance)
|