Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package net.buildtheearth.buildteamtools.modules.common.metrics;

import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Counts feature invocations since the last metrics submission.
* <p>
* {@link #record(UsageFeature)} counts a feature with no further breakdown.
* {@link #record(UsageDetail)} counts that part and its parent feature, so the summary charts
* stay aligned with the drilldown pie.
*/
public final class FeatureUsage {

private static final EnumMap<UsageFeature, AtomicInteger> COUNTS = new EnumMap<>(UsageFeature.class);
private static final EnumMap<UsageDetail, AtomicInteger> DETAILS = new EnumMap<>(UsageDetail.class);

static {
for (UsageFeature feature : UsageFeature.values())
COUNTS.put(feature, new AtomicInteger());
for (UsageDetail detail : UsageDetail.values())
DETAILS.put(detail, new AtomicInteger());
}

private FeatureUsage() {
}

public static void record(UsageFeature feature) {
COUNTS.get(feature).incrementAndGet();
}

public static void record(UsageDetail detail) {
record(detail.feature());
DETAILS.get(detail).incrementAndGet();
}

/**
* Returns every feature, including those with zero uses, then resets the feature counters.
*/
static Map<String, Integer> drain() {
Map<String, Integer> snapshot = new LinkedHashMap<>();
for (UsageFeature feature : UsageFeature.values())
snapshot.put(feature.chartId(), COUNTS.get(feature).getAndSet(0));
return snapshot;
}

/**
* Returns every part of the features that have a breakdown, including zeros, then resets those counters.
*/
static Map<String, Map<String, Integer>> drainDetails() {
Map<String, Map<String, Integer>> snapshot = new LinkedHashMap<>();
for (UsageDetail detail : UsageDetail.values()) {
snapshot.computeIfAbsent(detail.feature().chartId(), ignored -> new LinkedHashMap<>())
.put(detail.chartId(), DETAILS.get(detail).getAndSet(0));
}
return snapshot;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,113 @@
package net.buildtheearth.buildteamtools.modules.common.metrics;

import net.buildtheearth.buildteamtools.modules.Module;
import net.buildtheearth.buildteamtools.modules.ModuleHandler;
import net.buildtheearth.buildteamtools.modules.navigation.NavigationModule;
import net.buildtheearth.buildteamtools.modules.navigation.components.bluemap.BluemapComponent;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.AdvancedPie;
import org.bstats.charts.DrilldownPie;
import org.bstats.charts.SimpleBarChart;
import org.bukkit.plugin.java.JavaPlugin;

public class MetricsManager {
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
* bStats metrics for BuildTeamTools.
* <p>
* {@code feature_uses} is a bar chart that lists every tracked feature. A zero means it was not used
* in that submission. It shares one snapshot with {@code feature_details} so a submission does not
* reset the counters twice.
* <p>
* BlueMap has no player action in this plugin. It is reported as 1 while the integration is enabled.
* <p>
* {@code feature_details} is a drilldown pie. Clicking navigator, warps, address, or kml opens the
* part that was used. It shares the snapshot with the summary charts.
*/
public final class MetricsManager {

private static final int BSTATS_PLUGIN_ID = 29076;
/** Long enough for every chart in one submission to share a snapshot, shorter than the 30 minute submit interval. */
private static final long SNAPSHOT_REUSE_NANOS = TimeUnit.MINUTES.toNanos(2);

private static Metrics metrics;
private static PublishedSnapshot published;

private MetricsManager() {
}

public static void init(JavaPlugin plugin) {
new Metrics(plugin, BSTATS_PLUGIN_ID);
if (metrics != null)
metrics.shutdown();

Metrics created = new Metrics(plugin, BSTATS_PLUGIN_ID);
created.addCustomChart(new SimpleBarChart("feature_uses", MetricsManager::featureUseCounts));
created.addCustomChart(new DrilldownPie("feature_details", MetricsManager::featureDetails));
created.addCustomChart(new AdvancedPie("enabled_modules", MetricsManager::enabledModules));
metrics = created;
}

static Map<String, Integer> featureUseCounts() {
return featureUseCounts(System.nanoTime());
}

static Map<String, Integer> featureUseCounts(long nowNanos) {
return new LinkedHashMap<>(publish(nowNanos).counts());
}

static Map<String, Map<String, Integer>> featureDetails() {
return featureDetails(System.nanoTime());
}

static Map<String, Map<String, Integer>> featureDetails(long nowNanos) {
Map<String, Map<String, Integer>> copy = new LinkedHashMap<>();
publish(nowNanos).details().forEach((feature, parts) -> copy.put(feature, new LinkedHashMap<>(parts)));
return copy;
}

private static PublishedSnapshot publish(long nowNanos) {
synchronized (MetricsManager.class) {
if (published != null && nowNanos >= published.atNanos() && nowNanos - published.atNanos() < SNAPSHOT_REUSE_NANOS)
return published;

Map<String, Integer> counts = FeatureUsage.drain();
counts.put("bluemap", blueMapInUse());
published = new PublishedSnapshot(counts, FeatureUsage.drainDetails(), nowNanos);
return published;
}
}

static Map<String, Integer> enabledModules() {
Map<String, Integer> enabled = new LinkedHashMap<>();
for (Module module : ModuleHandler.getInstance().getModules())
if (module.isEnabled())
enabled.put(module.getModuleName(), 1);
return enabled;
}

private static int blueMapInUse() {
NavigationModule navigation = NavigationModule.getInstance();
if (navigation == null)
return 0;

BluemapComponent bluemap = navigation.getBluemapComponent();
return bluemap != null && bluemap.isEnabled() ? 1 : 0;
}

static void resetForTests() {
synchronized (MetricsManager.class) {
published = null;
}
FeatureUsage.drain();
FeatureUsage.drainDetails();
}

private record PublishedSnapshot(
Map<String, Integer> counts,
Map<String, Map<String, Integer>> details,
long atNanos
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.buildtheearth.buildteamtools.modules.common.metrics;

/**
* A part of a {@link UsageFeature} shown when that feature is opened on the bStats drilldown pie.
*/
public enum UsageDetail {
NAVIGATOR_MENU(UsageFeature.NAVIGATOR, "menu"),
NAVIGATOR_ITEM(UsageFeature.NAVIGATOR, "item"),
NAVIGATOR_EXPLORE(UsageFeature.NAVIGATOR, "explore"),
NAVIGATOR_COUNTRIES(UsageFeature.NAVIGATOR, "countries"),
NAVIGATOR_STATES(UsageFeature.NAVIGATOR, "states"),
NAVIGATOR_CITIES(UsageFeature.NAVIGATOR, "cities"),
NAVIGATOR_BUILD(UsageFeature.NAVIGATOR, "build"),
NAVIGATOR_PLOTSYSTEM(UsageFeature.NAVIGATOR, "plotsystem"),
NAVIGATOR_TUTORIALS(UsageFeature.NAVIGATOR, "tutorials"),
NAVIGATOR_TOGGLE(UsageFeature.NAVIGATOR, "toggle"),
NAVIGATOR_BUILDTEAM(UsageFeature.NAVIGATOR, "buildteam"),

WARPS_MENU(UsageFeature.WARPS, "menu"),
WARPS_TELEPORT(UsageFeature.WARPS, "teleport"),
WARPS_CREATE(UsageFeature.WARPS, "create"),
WARPS_MIGRATE(UsageFeature.WARPS, "migrate"),

ADDRESS_GET(UsageFeature.ADDRESS, "get"),
ADDRESS_TELEPORT(UsageFeature.ADDRESS, "teleport"),

KML_GEOPOINTS(UsageFeature.KML, "geopoints"),
KML_GEOPATH(UsageFeature.KML, "geopath"),
KML_GEORING(UsageFeature.KML, "georing"),
KML_GEOSURFACE(UsageFeature.KML, "geosurface"),
KML_UNDO(UsageFeature.KML, "undo");

private final UsageFeature feature;
private final String chartId;

UsageDetail(UsageFeature feature, String chartId) {
this.feature = feature;
this.chartId = chartId;
}

public UsageFeature feature() {
return feature;
}

public String chartId() {
return chartId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.buildtheearth.buildteamtools.modules.common.metrics;

/**
* Features whose invocations are reported to bStats.
* <p>
* Plot system and the sign text generator have no live player entry point, so they are not listed here.
* Whether those modules stay enabled is covered by the enabled-modules chart.
*/
public enum UsageFeature {
HOUSE("house"),
ROAD("road"),
RAIL("rail"),
TREE("tree"),
FIELD("field"),
KML("kml"),
NAVIGATOR("navigator"),
WARPS("warps"),
TPLL("tpll"),
ADDRESS("address"),
MAP("map"),
STATS("stats"),
BLOCK_PALETTE("block_palette");

private final String chartId;

UsageFeature(String chartId) {
this.chartId = chartId;
}

public String chartId() {
return chartId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public void generate(Player p) {
if (!checkForPlayer(p))
return;

recordUse();
new FieldScripts(p, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public void generate(Player p) {
if (!checkForPlayer(p))
return;

recordUse();
new HouseScripts(p, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import net.buildtheearth.buildteamtools.BuildTeamTools;
import net.buildtheearth.buildteamtools.modules.Module;
import net.buildtheearth.buildteamtools.modules.common.commands.BttCommandManager;
import net.buildtheearth.buildteamtools.modules.common.metrics.FeatureUsage;
import net.buildtheearth.buildteamtools.modules.common.metrics.UsageDetail;
import net.buildtheearth.buildteamtools.modules.common.metrics.UsageFeature;
import net.buildtheearth.buildteamtools.modules.navigation.NavUtils;
import net.buildtheearth.buildteamtools.utils.BlockLocation;
import net.buildtheearth.buildteamtools.utils.LineRasterization;
Expand Down Expand Up @@ -268,6 +271,7 @@ public boolean processKml(@NonNull Block senderBlock, String[] args) {
return false;
}

recordKml(blockCreationCommand);
String kml_content = String.join(" ", args);

Material matchedBlockMaterial = Material.matchMaterial(blocktypeString);
Expand Down Expand Up @@ -498,6 +502,7 @@ public boolean processKml(@NonNull Block senderBlock, String[] args) {
* @return true if the CommandBlock was created successfully
*/
public boolean createPasteUI(Player player, String alias, String @NonNull [] args) {
recordKml(alias);
//The command either creates a command-block at the player location
//arguments are an
// optional blocktype
Expand Down Expand Up @@ -580,6 +585,7 @@ public boolean undoCommand(Player player) {
return false;
}

FeatureUsage.record(UsageDetail.KML_UNDO);
ChangeTransaction transaction = playerHistory.pop();
transaction.undo();
player.sendMessage(String.format("undo successful. Restored %d blocks.", transaction.size()));
Expand Down Expand Up @@ -673,6 +679,20 @@ public boolean undoCommand(Player player) {
return mcLines;
}

private static void recordKml(@NonNull String command) {
UsageDetail detail = switch (command) {
case GEOPOINTS_COMMAND -> UsageDetail.KML_GEOPOINTS;
case GEOPATH_COMMAND -> UsageDetail.KML_GEOPATH;
case GEORING_COMMAND -> UsageDetail.KML_GEORING;
case GEOSURFACE_COMMAND -> UsageDetail.KML_GEOSURFACE;
default -> null;
};
if (detail == null)
FeatureUsage.record(UsageFeature.KML);
else
FeatureUsage.record(detail);
}

/**
* Maps command aliases to their corresponding block creation modes.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public void generate(Player player) {
return;
}

recordUse();
new RailScripts(player, this, () -> preparingPlayers.remove(player.getUniqueId()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public void generate(Player p) {
if (!GeneratorModule.getInstance().getRoad().checkForPlayer(p))
return;

recordUse();
new RoadScripts(p, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public void generate(Player p) {
if (!checkForPlayer(p))
return;

recordUse();
TreeScripts.treescript_v_1_0(p, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.alpsbte.alpslib.utils.WikiDocumented;
import lombok.Getter;
import net.buildtheearth.buildteamtools.modules.ModuleComponent;
import net.buildtheearth.buildteamtools.modules.common.metrics.FeatureUsage;
import net.buildtheearth.buildteamtools.modules.common.metrics.UsageFeature;
import net.buildtheearth.buildteamtools.modules.generator.components.field.FieldSettings;
import net.buildtheearth.buildteamtools.modules.generator.components.house.HouseSettings;
import net.buildtheearth.buildteamtools.modules.generator.components.rail.RailSettings;
Expand Down Expand Up @@ -39,6 +41,19 @@ protected GeneratorComponent(@NonNull GeneratorType type) {

public abstract void generate(Player p);

/**
* Records a generation that got past the feature's own checks.
*/
protected void recordUse() {
FeatureUsage.record(switch (generatorType) {
case HOUSE -> UsageFeature.HOUSE;
case ROAD -> UsageFeature.ROAD;
case RAIL -> UsageFeature.RAIL;
case TREE -> UsageFeature.TREE;
case FIELD -> UsageFeature.FIELD;
});
}

public void analyzeCommand(Player p, String[] args) {
if (isHelpCommand(args)) {
sendHelp(p);
Expand Down
Loading
Loading