diff --git a/bukkit/src/main/java/dev/faststats/bukkit/BukkitMetricsImpl.java b/bukkit/src/main/java/dev/faststats/bukkit/BukkitMetricsImpl.java index 13eda88..321f2d3 100644 --- a/bukkit/src/main/java/dev/faststats/bukkit/BukkitMetricsImpl.java +++ b/bukkit/src/main/java/dev/faststats/bukkit/BukkitMetricsImpl.java @@ -33,7 +33,7 @@ private BukkitMetricsImpl(SimpleMetrics.Factory factory, Plugin plugin, Path } private boolean checkOnlineMode() { - return tryOrEmpty(server.getServerConfig()::isProxyOnlineMode) + return tryOrEmpty(() -> server.getServerConfig().isProxyOnlineMode()) .or(() -> tryOrEmpty(this::isProxyOnlineMode)) .orElseGet(server::getOnlineMode); } @@ -52,12 +52,14 @@ private boolean isProxyOnlineMode() { } @Override - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "Convert2MethodRef"}) protected void appendDefaultData(JsonObject charts) { var pluginVersion = tryOrEmpty(() -> plugin.getPluginMeta().getVersion()) .orElseGet(() -> plugin.getDescription().getVersion()); - var minecraftVersion = tryOrEmpty(server::getMinecraftVersion) - .orElseGet(() -> server.getBukkitVersion().split("-", 2)[0]); + + var minecraftVersion = tryOrEmpty(() -> server.getMinecraftVersion()) + .or(() -> tryOrEmpty(() -> server.getBukkitVersion().split("-", 2)[0])) + .orElseGet(() -> server.getVersion().split("\\(MC: |\\)", 3)[1]); charts.addProperty("minecraft_version", minecraftVersion); charts.addProperty("online_mode", checkOnlineMode()); diff --git a/core/src/main/java/dev/faststats/core/SimpleMetrics.java b/core/src/main/java/dev/faststats/core/SimpleMetrics.java index a0a2d8c..9f0e857 100644 --- a/core/src/main/java/dev/faststats/core/SimpleMetrics.java +++ b/core/src/main/java/dev/faststats/core/SimpleMetrics.java @@ -26,6 +26,7 @@ import java.util.Properties; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -110,7 +111,7 @@ private void startSubmitting(long initialDelay, long period, TimeUnit unit) { } var enabled = Boolean.parseBoolean(System.getProperty("faststats.enabled", "true")); - + if (!config.enabled() || !enabled) { warn("Metrics disabled, not starting submission"); return; @@ -128,21 +129,34 @@ private void startSubmitting(long initialDelay, long period, TimeUnit unit) { }); info("Starting metrics submission"); - executor.scheduleAtFixedRate(this::submitData, initialDelay, period, unit); + executor.scheduleAtFixedRate(() -> { + try { + submitAsync(); + } catch (Throwable t) { + error("Failed to submit metrics", t); + } + }, initialDelay, period, unit); } protected boolean isSubmitting() { return executor != null && !executor.isShutdown(); } - protected void submitData() { + protected CompletableFuture submitAsync() throws IOException { var data = createData().toString(); var bytes = data.getBytes(StandardCharsets.UTF_8); + + info("Uncompressed data: " + data); + try (var byteOutput = new ByteArrayOutputStream(); var output = new GZIPOutputStream(byteOutput)) { + output.write(bytes); output.finish(); + var compressed = byteOutput.toByteArray(); + info("Compressed size: " + compressed.length + " bytes"); + var request = HttpRequest.newBuilder() .POST(HttpRequest.BodyPublishers.ofByteArray(compressed)) .header("Content-Encoding", "gzip") @@ -154,31 +168,34 @@ protected void submitData() { .build(); info("Sending metrics to: " + url); - info("Uncompressed data: " + data); - info("Compressed size: " + compressed.length + " bytes"); - - var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); - var statusCode = response.statusCode(); - var body = response.body(); - - if (statusCode >= 200 && statusCode < 300) { - info("Metrics submitted with status code: " + statusCode + " (" + body + ")"); - } else if (statusCode >= 300 && statusCode < 400) { - warn("Received redirect response from metrics server: " + statusCode + " (" + body + ")"); - } else if (statusCode >= 400 && statusCode < 500) { - error("Submitted invalid request to metrics server: " + statusCode + " (" + body + ")", null); - } else if (statusCode >= 500 && statusCode < 600) { - error("Received server error response from metrics server: " + statusCode + " (" + body + ")", null); - } else { - warn("Received unexpected response from metrics server: " + statusCode + " (" + body + ")"); - } - - } catch (HttpConnectTimeoutException e) { - error("Metrics submission timed out after 3 seconds: " + url, null); - } catch (ConnectException e) { - error("Failed to connect to metrics server: " + url, null); - } catch (Exception e) { - error("Failed to submit metrics", e); + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)).thenApply(response -> { + var statusCode = response.statusCode(); + var body = response.body(); + + if (statusCode >= 200 && statusCode < 300) { + info("Metrics submitted with status code: " + statusCode + " (" + body + ")"); + return true; + } else if (statusCode >= 300 && statusCode < 400) { + warn("Received redirect response from metrics server: " + statusCode + " (" + body + ")"); + } else if (statusCode >= 400 && statusCode < 500) { + error("Submitted invalid request to metrics server: " + statusCode + " (" + body + ")", null); + } else if (statusCode >= 500 && statusCode < 600) { + error("Received server error response from metrics server: " + statusCode + " (" + body + ")", null); + } else { + warn("Received unexpected response from metrics server: " + statusCode + " (" + body + ")"); + } + return false; + }).exceptionally(throwable -> { + var cause = throwable.getCause() != null ? throwable.getCause() : throwable; + if (cause instanceof HttpConnectTimeoutException) { + error("Metrics submission timed out after 3 seconds: " + url, null); + } else if (cause instanceof ConnectException) { + error("Failed to connect to metrics server: " + url, null); + } else { + error("Failed to submit metrics", throwable); + } + return false; + }); } } @@ -195,7 +212,7 @@ protected JsonObject createData() { this.charts.forEach(chart -> { try { chart.getData().ifPresent(chartData -> charts.add(chart.getId(), chartData)); - } catch (Exception e) { + } catch (Throwable e) { error("Failed to build chart data: " + chart.getId(), e); } }); diff --git a/core/src/test/java/dev/faststats/BukkitMetricsTest.java b/core/src/test/java/dev/faststats/BukkitMetricsTest.java index a3d69de..6d6faed 100644 --- a/core/src/test/java/dev/faststats/BukkitMetricsTest.java +++ b/core/src/test/java/dev/faststats/BukkitMetricsTest.java @@ -2,25 +2,15 @@ import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.UUID; -import java.util.zip.GZIPOutputStream; + +import static org.junit.jupiter.api.Assumptions.assumeTrue; public class BukkitMetricsTest { @Test public void testCreateData() throws IOException { - var mock = new MockMetrics(UUID.randomUUID(), "bba4a14eac38779007a6fda4814381ab", true); - var data = mock.createData(); - var bytes = data.toString().getBytes(StandardCharsets.UTF_8); - try (var byteOutput = new ByteArrayOutputStream(); - var output = new GZIPOutputStream(byteOutput)) { - output.write(bytes); - output.finish(); - var compressed = byteOutput.toByteArray(); - mock.printInfo(new String(compressed, StandardCharsets.UTF_8) + " (" + compressed.length + " bytes)"); - mock.printInfo(new String(bytes, StandardCharsets.UTF_8) + " (" + bytes.length + " bytes)"); - } + var mock = new MockMetrics(UUID.randomUUID(), "24f9fc423ed06194065a42d00995c600", true); + assumeTrue(mock.submitAsync().join(), "For this test to run, the server must be running"); } } diff --git a/core/src/test/java/dev/faststats/MockMetrics.java b/core/src/test/java/dev/faststats/MockMetrics.java index 710842f..cbc0047 100644 --- a/core/src/test/java/dev/faststats/MockMetrics.java +++ b/core/src/test/java/dev/faststats/MockMetrics.java @@ -6,14 +6,16 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.io.IOException; import java.net.URI; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; @NullMarked public class MockMetrics extends SimpleMetrics { public MockMetrics(UUID serverId, @Token String token, boolean debug) { - super(new SimpleMetrics.Config(serverId, true, debug), Set.of(), token, URI.create("http://localhost:5000"), debug); + super(new SimpleMetrics.Config(serverId, true, debug), Set.of(), token, URI.create("http://localhost:5000/v1/collect"), debug); } @Override @@ -32,6 +34,11 @@ protected void printWarning(String message) { System.out.println(message); } + @Override + public CompletableFuture submitAsync() throws IOException { + return super.submitAsync(); + } + @Override public JsonObject createData() { return super.createData(); diff --git a/gradle.properties b/gradle.properties index 304b5f6..8387a74 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.7.4 +version=0.7.5