Update bStats Metrics and add wrapper class for new graphs (#3451)

This PR updates the version of the bStats Metrics class to the latest version, supporting plugin IDs in place of just plugin names. It also adds the following graphs:
- Active permissions backend
- Active economy backend
- Whether or not a command has been used as a bar chart (pending bStats backend implementation)
- Version history graph as a multiline graph (also pending bStats impl)

It also removes the weird `getMetrics` and `setMetrics` APIs which should never have been API in the first place.
This commit is contained in:
MD 2020-07-05 19:30:01 +01:00 committed by GitHub
parent 8f86849aec
commit 1be3daf0b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 212 additions and 58 deletions

View File

@ -22,7 +22,7 @@ import com.earth2me.essentials.items.AbstractItemDb;
import com.earth2me.essentials.items.CustomItemResolver;
import com.earth2me.essentials.items.FlatItemDb;
import com.earth2me.essentials.items.LegacyItemDb;
import com.earth2me.essentials.metrics.Metrics;
import com.earth2me.essentials.metrics.MetricsWrapper;
import com.earth2me.essentials.perm.PermissionsHandler;
import com.earth2me.essentials.register.payment.Methods;
import com.earth2me.essentials.signs.SignBlockListener;
@ -103,7 +103,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
private transient UserMap userMap;
private transient ExecuteTimer execTimer;
private transient I18n i18n;
private transient Metrics metrics;
private transient MetricsWrapper metrics;
private transient EssentialsTimer timer;
private final transient Set<String> vanishedPlayers = new LinkedHashSet<>();
private transient Method oldGetOnlinePlayers;
@ -305,12 +305,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
for (World w : Bukkit.getWorlds())
addDefaultBackPermissionsToWorld(w);
metrics = new Metrics(this);
if (metrics.isEnabled()) {
getLogger().info("Starting Metrics. Opt-out using the global bStats config.");
} else {
getLogger().info("Metrics disabled per bStats config.");
}
metrics = new MetricsWrapper(this, 858, true);
final String timeroutput = execTimer.end();
if (getSettings().isDebug()) {
@ -518,6 +513,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
@Override
public boolean onCommand(final CommandSender sender, final Command command, final String commandLabel, final String[] args) {
metrics.markCommand(command.getName(), true);
return onCommandEssentials(sender, command, commandLabel, args, Essentials.class.getClassLoader(), "com.earth2me.essentials.commands.Command", "essentials.", null);
}
@ -686,16 +682,6 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return kits;
}
@Override
public Metrics getMetrics() {
return metrics;
}
@Override
public void setMetrics(Metrics metrics) {
this.metrics = metrics;
}
@Deprecated
@Override
public User getUser(final Object base) {

View File

@ -3,7 +3,6 @@ package com.earth2me.essentials;
import com.earth2me.essentials.api.IItemDb;
import com.earth2me.essentials.api.IJails;
import com.earth2me.essentials.api.IWarps;
import com.earth2me.essentials.metrics.Metrics;
import com.earth2me.essentials.perm.PermissionsHandler;
import com.earth2me.essentials.register.payment.Methods;
import net.ess3.provider.ServerStateProvider;
@ -93,10 +92,6 @@ public interface IEssentials extends Plugin {
UserMap getUserMap();
Metrics getMetrics();
void setMetrics(Metrics metrics);
EssentialsTimer getTimer();
@Deprecated

View File

@ -3,6 +3,7 @@ package com.earth2me.essentials.metrics;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
@ -67,6 +68,9 @@ public class Metrics {
// The plugin
private final Plugin plugin;
// The plugin id
private final int pluginId;
// A list with all custom charts
private final List<CustomChart> charts = new ArrayList<>();
@ -74,12 +78,15 @@ public class Metrics {
* Class constructor.
*
* @param plugin The plugin which stats should be submitted.
* @param pluginId The id of the plugin.
* It can be found at <a href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/
public Metrics(Plugin plugin) {
public Metrics(Plugin plugin, int pluginId) {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null!");
}
this.plugin = plugin;
this.pluginId = pluginId;
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
@ -194,6 +201,7 @@ public class Metrics {
String pluginVersion = plugin.getDescription().getVersion();
data.addProperty("pluginName", pluginName); // Append the name of the plugin
data.addProperty("id", pluginId); // Append the id of the plugin
data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin
JsonArray customCharts = new JsonArray();
for (CustomChart customChart : charts) {
@ -288,7 +296,6 @@ public class Metrics {
if (logFailedRequests) {
this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e);
}
continue; // continue looping since we cannot do any other thing.
}
}
} catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { }
@ -327,7 +334,7 @@ public class Metrics {
throw new IllegalAccessException("This method must not be called from the main thread!");
}
if (logSentData) {
plugin.getLogger().info("Sending data to bStats: " + data.toString());
plugin.getLogger().info("Sending data to bStats: " + data);
}
HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
@ -345,22 +352,20 @@ public class Metrics {
// Send data
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.write(compressedData);
outputStream.flush();
outputStream.close();
InputStream inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
outputStream.write(compressedData);
}
StringBuilder builder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
bufferedReader.close();
if (logResponseStatusText) {
plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString());
plugin.getLogger().info("Sent data to bStats and received response: " + builder);
}
}
@ -376,9 +381,9 @@ public class Metrics {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
gzip.write(str.getBytes(StandardCharsets.UTF_8));
gzip.close();
try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
gzip.write(str.getBytes(StandardCharsets.UTF_8));
}
return outputStream.toByteArray();
}
@ -655,7 +660,7 @@ public class Metrics {
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
JsonArray categoryValues = new JsonArray();
categoryValues.add(entry.getValue());
categoryValues.add(new JsonPrimitive(entry.getValue()));
values.add(entry.getKey(), categoryValues);
}
data.add("values", values);
@ -699,7 +704,7 @@ public class Metrics {
allSkipped = false;
JsonArray categoryValues = new JsonArray();
for (int categoryValue : entry.getValue()) {
categoryValues.add(categoryValue);
categoryValues.add(new JsonPrimitive(categoryValue));
}
values.add(entry.getKey(), categoryValues);
}
@ -712,4 +717,4 @@ public class Metrics {
}
}
}
}

View File

@ -0,0 +1,137 @@
package com.earth2me.essentials.metrics;
import com.earth2me.essentials.Essentials;
import com.earth2me.essentials.register.payment.Methods;
import com.google.common.collect.ImmutableList;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MetricsWrapper {
private final Essentials ess;
private final Metrics metrics;
private final Map<String, Boolean> commands = new HashMap<>();
private final Plugin plugin;
private static boolean hasWarned = false;
private static final List<String> KNOWN_FORCED_METRICS = ImmutableList.of("ChatControl");
public MetricsWrapper(Plugin plugin, int pluginId, boolean includeCommands) {
this.plugin = plugin;
this.ess = (Essentials) Bukkit.getPluginManager().getPlugin("Essentials");
this.metrics = new Metrics(plugin, pluginId);
if (metrics.isEnabled()) {
plugin.getLogger().info("Starting Metrics. Opt-out using the global bStats config.");
} else {
plugin.getLogger().info("Metrics disabled per bStats config.");
}
checkForcedMetrics();
addPermsChart();
addEconomyChart();
// bStats' backend currently doesn't support multi-line charts or advanced bar charts
// These are included for when bStats is ready to accept this data
addVersionHistoryChart();
if (includeCommands) addCommandsChart();
}
public void markCommand(String command, boolean state) {
commands.put(command, state);
}
public void addCustomChart(Metrics.CustomChart chart) {
metrics.addCustomChart(chart);
}
private void addPermsChart() {
metrics.addCustomChart(new Metrics.DrilldownPie("permsPlugin", () -> {
Map<String, Map<String, Integer>> result = new HashMap<>();
String handler = ess.getPermissionsHandler().getName();
Map<String, Integer> backend = new HashMap<>();
backend.put(ess.getPermissionsHandler().getBackendName(), 1);
result.put(handler, backend);
return result;
}));
}
private void addEconomyChart() {
metrics.addCustomChart(new Metrics.DrilldownPie("econPlugin", () -> {
Map<String, Map<String, Integer>> result = new HashMap<>();
Map<String, Integer> backend = new HashMap<>();
backend.put(Methods.getMethod().getPlugin().getName(), 1);
result.put(Methods.getMethod().getName(), backend);
return result;
}));
}
private void addVersionHistoryChart() {
metrics.addCustomChart(new Metrics.MultiLineChart("versionHistory", () -> {
HashMap<String, Integer> result = new HashMap<>();
result.put(plugin.getDescription().getVersion(), 1);
return result;
}));
}
private void addCommandsChart() {
for (String command : plugin.getDescription().getCommands().keySet()) {
markCommand(command, false);
}
metrics.addCustomChart(new Metrics.AdvancedBarChart("commands", () -> {
Map<String, int[]> result = new HashMap<>();
for (Map.Entry<String, Boolean> entry : commands.entrySet()) {
if (entry.getValue()) {
result.put(entry.getKey(), new int[]{1,0});
} else {
result.put(entry.getKey(), new int[]{0,1});
}
}
return result;
}));
}
private void checkForcedMetrics() {
if (hasWarned) return;
hasWarned = true;
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Identifies bStats classes
if (KNOWN_FORCED_METRICS.contains(JavaPlugin.getProvidingPlugin(service).getName())) {
warnForcedMetrics(service);
} else {
try {
service.getDeclaredField("pluginId"); // Only present in recent bStats classes, which should also have the enabled field unless modified
} catch (NoSuchFieldException e) {
// Old bStats class found so "enabled" field detection is unreliable.
break;
}
try {
service.getDeclaredField("enabled"); // In modified forced metrics classes, this will fail
} catch (NoSuchFieldException e) {
warnForcedMetrics(service);
}
}
} catch (NoSuchFieldException ignored) {}
}
});
}
private void warnForcedMetrics(Class<?> service) {
Plugin servicePlugin = JavaPlugin.getProvidingPlugin(service);
plugin.getLogger().severe("WARNING: Potential forced metrics collection by plugin '" + servicePlugin.getName() + "' v" + servicePlugin.getDescription().getVersion());
plugin.getLogger().severe("Your server is running a plugin that may not respect bStats' opt-out settings.");
plugin.getLogger().severe("This may cause data to be uploaded to bStats.org for plugins that use bStats, even if you've opted out in the bStats config.");
plugin.getLogger().severe("Please report this to bStats and to the authors of '" + servicePlugin.getName() + "'. (Offending class: " + service.getName() + ")");
}
}

View File

@ -29,5 +29,7 @@ public interface IPermissionsHandler {
void unregisterContexts();
String getBackendName();
boolean tryProvider();
}

View File

@ -102,6 +102,11 @@ public class PermissionsHandler implements IPermissionsHandler {
handler.unregisterContexts();
}
@Override
public String getBackendName() {
return handler.getBackendName();
}
@Override
public boolean tryProvider() {
return true;

View File

@ -5,6 +5,7 @@ import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Arrays;
import java.util.List;
@ -73,6 +74,11 @@ public abstract class AbstractVaultHandler extends SuperpermsHandler {
return null;
}
@Override
public String getBackendName() {
return JavaPlugin.getProvidingPlugin(perms.getClass()).getName();
}
boolean canLoad() {
if (Bukkit.getPluginManager().getPlugin("Vault") == null) return false;
try {

View File

@ -23,6 +23,11 @@ public class ConfigPermissionsHandler extends SuperpermsHandler {
return ess.getSettings().isPlayerCommand(cmds[cmds.length - 1]) || super.hasPermission(base, node);
}
@Override
public String getBackendName() {
return "Essentials";
}
@Override
public boolean tryProvider() {
return true;

View File

@ -82,6 +82,11 @@ public class SuperpermsHandler implements IPermissionsHandler {
public void unregisterContexts() {
}
@Override
public String getBackendName() {
return getEnabledPermsPlugin();
}
@Override
public boolean tryProvider() {
return getEnabledPermsPlugin() != null;

View File

@ -24,7 +24,7 @@ public interface Method {
* @see #getName()
* @see #getVersion()
*/
Object getPlugin();
Plugin getPlugin();
/**
* Returns the actual name of this method.

View File

@ -1,6 +1,7 @@
package com.earth2me.essentials.antibuild;
import com.earth2me.essentials.metrics.Metrics;
import com.earth2me.essentials.metrics.MetricsWrapper;
import org.bukkit.Material;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
@ -15,7 +16,7 @@ public class EssentialsAntiBuild extends JavaPlugin implements IAntiBuild {
private final transient Map<AntiBuildConfig, Boolean> settingsBoolean = new EnumMap<>(AntiBuildConfig.class);
private final transient Map<AntiBuildConfig, List<Material>> settingsList = new EnumMap<>(AntiBuildConfig.class);
private transient EssentialsConnect ess = null;
private transient Metrics metrics = null;
private transient MetricsWrapper metrics = null;
@Override
public void onEnable() {
@ -30,7 +31,7 @@ public class EssentialsAntiBuild extends JavaPlugin implements IAntiBuild {
pm.registerEvents(blockListener, this);
if (metrics == null) {
metrics = new Metrics(this);
metrics = new MetricsWrapper(this, 3813, false);
}
}

View File

@ -1,6 +1,7 @@
package com.earth2me.essentials.chat;
import com.earth2me.essentials.metrics.Metrics;
import com.earth2me.essentials.metrics.MetricsWrapper;
import net.ess3.api.IEssentials;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.plugin.PluginManager;
@ -16,7 +17,7 @@ import static com.earth2me.essentials.I18n.tl;
public class EssentialsChat extends JavaPlugin {
private transient Metrics metrics = null;
private transient MetricsWrapper metrics = null;
@Override
public void onEnable() {
@ -40,7 +41,7 @@ public class EssentialsChat extends JavaPlugin {
pluginManager.registerEvents(playerListenerHighest, this);
if (metrics == null) {
metrics = new Metrics(this);
metrics = new MetricsWrapper(this, 3814, false);
}
}

View File

@ -5,6 +5,8 @@ import static com.earth2me.essentials.I18n.tl;
import com.earth2me.essentials.metrics.Metrics;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.earth2me.essentials.metrics.MetricsWrapper;
import net.ess3.api.IEssentials;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@ -12,7 +14,7 @@ import org.bukkit.plugin.java.JavaPlugin;
public class EssentialsGeoIP extends JavaPlugin {
private transient Metrics metrics = null;
private transient MetricsWrapper metrics = null;
@Override
public void onEnable() {
@ -35,7 +37,7 @@ public class EssentialsGeoIP extends JavaPlugin {
getLogger().log(Level.INFO, "This product includes GeoLite2 data created by MaxMind, available from http://www.maxmind.com/.");
if (metrics == null) {
metrics = new Metrics(this);
metrics = new MetricsWrapper(this, 3815, false);
}
}

View File

@ -1,6 +1,7 @@
package com.earth2me.essentials.protect;
import com.earth2me.essentials.metrics.Metrics;
import com.earth2me.essentials.metrics.MetricsWrapper;
import com.earth2me.essentials.utils.VersionUtil;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@ -20,7 +21,7 @@ public class EssentialsProtect extends JavaPlugin implements IProtect {
private final Map<ProtectConfig, String> settingsString = new EnumMap<>(ProtectConfig.class);
private final Map<ProtectConfig, List<Material>> settingsList = new EnumMap<>(ProtectConfig.class);
private EssentialsConnect ess = null;
private transient Metrics metrics = null;
private transient MetricsWrapper metrics = null;
private final EmergencyListener emListener = new EmergencyListener(this);
@ -36,7 +37,7 @@ public class EssentialsProtect extends JavaPlugin implements IProtect {
initialize(pm, essPlugin);
if (metrics == null) {
metrics = new Metrics(this);
metrics = new MetricsWrapper(this, 3816, false);
}
}

View File

@ -1,6 +1,6 @@
package com.earth2me.essentials.spawn;
import com.earth2me.essentials.metrics.Metrics;
import com.earth2me.essentials.metrics.MetricsWrapper;
import net.ess3.api.IEssentials;
import org.bukkit.Location;
import org.bukkit.command.Command;
@ -19,7 +19,7 @@ import static com.earth2me.essentials.I18n.tl;
public class EssentialsSpawn extends JavaPlugin implements IEssentialsSpawn {
private transient IEssentials ess;
private transient SpawnStorage spawns;
private transient Metrics metrics = null;
private transient MetricsWrapper metrics = null;
@Override
public void onEnable() {
@ -51,7 +51,7 @@ public class EssentialsSpawn extends JavaPlugin implements IEssentialsSpawn {
}
if (metrics == null) {
metrics = new Metrics(this);
metrics = new MetricsWrapper(this, 3817, true);
}
}
@ -61,6 +61,7 @@ public class EssentialsSpawn extends JavaPlugin implements IEssentialsSpawn {
@Override
public boolean onCommand(final CommandSender sender, final Command command, final String commandLabel, final String[] args) {
metrics.markCommand(command.getName(), true);
return ess.onCommandEssentials(sender, command, commandLabel, args, EssentialsSpawn.class.getClassLoader(), "com.earth2me.essentials.spawn.Command", "essentials.", spawns);
}

View File

@ -2,6 +2,7 @@ package com.earth2me.essentials.xmpp;
import com.earth2me.essentials.IEssentials;
import com.earth2me.essentials.metrics.Metrics;
import com.earth2me.essentials.metrics.MetricsWrapper;
import net.ess3.api.IUser;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@ -21,7 +22,7 @@ public class EssentialsXMPP extends JavaPlugin implements IEssentialsXMPP {
private transient UserManager users;
private transient XMPPManager xmpp;
private transient IEssentials ess;
private transient Metrics metrics = null;
private transient MetricsWrapper metrics = null;
static IEssentialsXMPP getInstance() {
return instance;
@ -51,7 +52,7 @@ public class EssentialsXMPP extends JavaPlugin implements IEssentialsXMPP {
ess.addReloadListener(xmpp);
if (metrics == null) {
metrics = new Metrics(this);
metrics = new MetricsWrapper(this, 3818, true);
metrics.addCustomChart(new Metrics.SimplePie("config-valid", () -> xmpp.isConfigValid() ? "yes" : "no"));
}
}
@ -66,6 +67,7 @@ public class EssentialsXMPP extends JavaPlugin implements IEssentialsXMPP {
@Override
public boolean onCommand(final CommandSender sender, final Command command, final String commandLabel, final String[] args) {
metrics.markCommand(command.getName(), true);
return ess.onCommandEssentials(sender, command, commandLabel, args, EssentialsXMPP.class.getClassLoader(), "com.earth2me.essentials.xmpp.Command", "essentials.", null);
}