Skip to content

Commit

Permalink
Add new analytics service
Browse files Browse the repository at this point in the history
  • Loading branch information
WalshyDev committed Dec 24, 2023
1 parent a56aacd commit 3a5e9dc
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 1 deletion.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ For more info see [bStats' Privacy Policy](https://bstats.org/privacy-policy)
Our [bStats Module](https://github.com/Slimefun/MetricsModule) is downloaded automatically when installing this Plugin, this module will automatically update on server starts independently from the main plugin. This way we can automatically roll out updates to the bStats module, in cases of severe performance issues for example where live data and insight into what is impacting performance can be crucial.
These updates can of course be disabled under `/plugins/Slimefun/config.yml`. To disable metrics collection as a whole, see the paragraph above.

---

Slimefun also uses it's own analytics system to collect anonymous information about the performance of this plugin.<br>
This is solely for statistical purposes, as we are interested in how it's performing for all servers.<br>
All available data is anonymous and aggregated, at no point can we see individual server information.<br>

You can also disable this behaviour under `/plugins/Slimefun/config.yml`.<br>

</details>

<details>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ public enum TestCase {
* being checked and why it is comparing IDs or meta.
* This is helpful for us to check into why input nodes are taking a while for servers.
*/
CARGO_INPUT_TESTING;
CARGO_INPUT_TESTING,

ANALYTICS;

TestCase() {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package io.github.thebusybiscuit.slimefun4.core.services;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import javax.annotation.ParametersAreNonnullByDefault;

import org.bukkit.plugin.Plugin;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import io.github.thebusybiscuit.slimefun4.core.debug.Debug;
import io.github.thebusybiscuit.slimefun4.core.debug.TestCase;
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;

/**
* This class represents an analytics service that sends data
* This data is used to analyse performance of this {@link Plugin}.
* <p>
* You can find more info in the README file of this Project on GitHub. <br>
*
* @author WalshyDev
*/
public class AnalyticsService {

private static final int VERSION = 1;
private static final String API_URL = "https://sf-analytics.walshy.dev/ingest";

private final boolean enabled;
private final HttpClient client = HttpClient.newHttpClient();

public AnalyticsService(Slimefun plugin) {
this.enabled = plugin.getConfig().getBoolean("metrics.analytics");

if (enabled) {
plugin.getLogger().info("Enabled Analytics Service");

// Send the timings data every minute
plugin.getServer().getScheduler()
.runTaskTimerAsynchronously(plugin, sendTimingsAnalytics(), 20 * 60, 20 * 60);
}
}

// Timings we'll send the average every minute
private Runnable sendTimingsAnalytics() {
return () -> {
double tickInterval = Slimefun.getTickerTask().getTickRate();
// This is currently used by bStats in a ranged way, we'll move this
double totalTimings = Slimefun.getProfiler().getAndResetAverageNanosecondTimings();
double avgPerMachine = Slimefun.getProfiler().getAverageTimingsPerMachine();

if (totalTimings == 0 || avgPerMachine == 0) {
Debug.log(TestCase.ANALYTICS, "Ignoring analytics data for server_timings as no data was found"
+ " - total: " + totalTimings + ", avg: " + avgPerMachine);
// Ignore if no data
return;
}

send("server_timings", new double[]{
tickInterval,
totalTimings,
avgPerMachine
}, null);
};
}

// Important: Keep the order of these doubles and blobs the same unless you increment the version number
// If a value is no longer used, just send null or replace it with a new value - don't shift the order
@ParametersAreNonnullByDefault
private void send(String id, double[] doubles, String[] blobs) {
// If not enabled, just ignore.
if (!enabled) return;

JsonObject object = new JsonObject();
object.addProperty("index", id);

JsonArray doublesArray = new JsonArray();
doublesArray.add(VERSION);
if (doubles != null) {
for (double d : doubles) {
doublesArray.add(d);
}
}
object.add("doubles", doublesArray);

JsonArray blobsArray = new JsonArray();
if (blobs != null) {
for (String s : blobs) {
blobsArray.add(s);
}
}
object.add("blobs", blobsArray);

Debug.log(TestCase.ANALYTICS, "Sending analytics data for " + id);
Debug.log(TestCase.ANALYTICS, object.toString());

// Send async, we do not care about the result. If it fails, that's fine.
client.sendAsync(HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.POST(HttpRequest.BodyPublishers.ofString(object.toString()))
.build(), HttpResponse.BodyHandlers.discarding());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.bukkit.block.Block;
import org.bukkit.scheduler.BukkitScheduler;

import com.google.common.util.concurrent.AtomicDouble;

import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon;
import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem;
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
Expand Down Expand Up @@ -87,6 +89,8 @@ public class SlimefunProfiler {

private final AtomicLong totalMsTicked = new AtomicLong();
private final AtomicInteger ticksPassed = new AtomicInteger();
private final AtomicLong totalNsTicked = new AtomicLong();
private final AtomicDouble averageTimingsPerMachine = new AtomicDouble();

/**
* This method terminates the {@link SlimefunProfiler}.
Expand Down Expand Up @@ -222,11 +226,14 @@ private void finishReport() {

totalElapsedTime = timings.values().stream().mapToLong(Long::longValue).sum();

averageTimingsPerMachine.getAndSet(timings.values().stream().mapToLong(Long::longValue).average().orElse(0));

/*
* We log how many milliseconds have been ticked, and how many ticks have passed
* This is so when bStats requests the average timings, they're super quick to figure out
*/
totalMsTicked.addAndGet(TimeUnit.NANOSECONDS.toMillis(totalElapsedTime));
totalNsTicked.addAndGet(totalElapsedTime);
ticksPassed.incrementAndGet();

if (!requests.isEmpty()) {
Expand Down Expand Up @@ -416,4 +423,26 @@ public long getAndResetAverageTimings() {

return l;
}

/**
* Get and reset the average nanosecond timing for this {@link SlimefunProfiler}.
*
* @return The average nanosecond timing for this {@link SlimefunProfiler}.
*/
public double getAndResetAverageNanosecondTimings() {
long l = totalNsTicked.get() / ticksPassed.get();
totalNsTicked.set(0);
ticksPassed.set(0);

return l;
}

/**
* Get and reset the average millisecond timing for each machine.
*
* @return The average millisecond timing for each machine.
*/
public double getAverageTimingsPerMachine() {
return averageTimingsPerMachine.getAndSet(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import io.github.thebusybiscuit.slimefun4.core.SlimefunRegistry;
import io.github.thebusybiscuit.slimefun4.core.commands.SlimefunCommand;
import io.github.thebusybiscuit.slimefun4.core.networks.NetworkManager;
import io.github.thebusybiscuit.slimefun4.core.services.AnalyticsService;
import io.github.thebusybiscuit.slimefun4.core.services.AutoSavingService;
import io.github.thebusybiscuit.slimefun4.core.services.BackupService;
import io.github.thebusybiscuit.slimefun4.core.services.BlockDataService;
Expand Down Expand Up @@ -182,6 +183,7 @@ public final class Slimefun extends JavaPlugin implements SlimefunAddon {
private final MinecraftRecipeService recipeService = new MinecraftRecipeService(this);
private final HologramsService hologramsService = new HologramsService(this);
private final SoundService soundService = new SoundService(this);
private AnalyticsService analyticsService;

// Some other things we need
private final IntegrationsManager integrations = new IntegrationsManager(this);
Expand Down Expand Up @@ -314,6 +316,7 @@ private void onPluginStart() {

// Setting up bStats
new Thread(metricsService::start, "Slimefun Metrics").start();
analyticsService = new AnalyticsService(this);

// Starting the Auto-Updater
if (config.getBoolean("options.auto-update")) {
Expand Down Expand Up @@ -904,6 +907,17 @@ public static SoundService getSoundService() {
return instance.metricsService;
}

/**
* This method returns the {@link AnalyticsService} of Slimefun.
* It is used to handle sending analytic information.
*
* @return The {@link AnalyticsService} for Slimefun
*/
public static @Nonnull AnalyticsService getAnalyticsService() {
validateInstance();
return instance.analyticsService;
}

/**
* This method returns the {@link GitHubService} of Slimefun.
* It is used to retrieve data from GitHub repositories.
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ talismans:

metrics:
auto-update: true
analytics: true

research-ranks:
- Chicken
Expand Down

0 comments on commit 3a5e9dc

Please sign in to comment.