From 82ab8c2c7c3f33b76c54be4a5cbc3855e2d9a3e3 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Fri, 15 Nov 2019 18:59:13 +0100 Subject: [PATCH] Add own metrics and save and load the state the render-manager of the sponge-plugin --- .../de/bluecolored/bluemap/core/BlueMap.java | 7 ++ .../bluemap/core/metrics/Metrics.java | 65 +++++++++++++++ .../bluemap/sponge/MapUpdateHandler.java | 11 +++ .../bluemap/sponge/RenderManager.java | 79 +++++++++++++++++++ .../bluemap/sponge/RenderTask.java | 58 ++++++++++++-- .../bluemap/sponge/SpongePlugin.java | 64 ++++++++++++++- 6 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java new file mode 100644 index 00000000..ad7f8cd2 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/BlueMap.java @@ -0,0 +1,7 @@ +package de.bluecolored.bluemap.core; + +public class BlueMap { + + public static final String VERSION = "0.0.0"; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java new file mode 100644 index 00000000..45fea632 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/metrics/Metrics.java @@ -0,0 +1,65 @@ +package de.bluecolored.bluemap.core.metrics; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import javax.net.ssl.HttpsURLConnection; + +import com.google.gson.JsonObject; + +import de.bluecolored.bluemap.core.BlueMap; + +public class Metrics { + + private static final String METRICS_REPORT_URL = "https://metrics.bluecolored.de/bluemap/"; + + public static void sendReportAsync(String implementation) { + new Thread(() -> sendReport(implementation)).start(); + } + + public static void sendReport(String implementation) { + JsonObject data = new JsonObject(); + data.addProperty("implementation", implementation); + data.addProperty("version", BlueMap.VERSION); + + try { + sendData(data.toString()); + } catch (Exception ex) {} + } + + private static String sendData(String data) throws MalformedURLException, IOException { + byte[] bytes = data.getBytes(StandardCharsets.UTF_8); + + HttpsURLConnection connection = (HttpsURLConnection) new URL(METRICS_REPORT_URL).openConnection(); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Content-Length", String.valueOf(bytes.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Connection", "close"); + connection.setRequestProperty("User-Agent", "BlueMap"); + connection.setDoOutput(true); + + try (OutputStream out = connection.getOutputStream()){ + out.write(bytes); + out.flush(); + } + + try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + String line; + StringBuilder builder = new StringBuilder(); + + while ((line = in.readLine()) != null) { + builder.append(line + "\n"); + } + + return builder.toString(); + } + + } + +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapUpdateHandler.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapUpdateHandler.java index 13b8ed02..529b6d0e 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapUpdateHandler.java +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/MapUpdateHandler.java @@ -111,4 +111,15 @@ public class MapUpdateHandler { return updateBuffer.size(); } + public void flushTileBuffer() { + RenderManager renderManager = SpongePlugin.getInstance().getRenderManager(); + + synchronized (updateBuffer) { + for (MapType map : updateBuffer.keySet()) { + renderManager.createTickets(map, updateBuffer.get(map)); + } + updateBuffer.clear(); + } + } + } diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderManager.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderManager.java index 2e3fc7cd..f25598a6 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderManager.java +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderManager.java @@ -1,5 +1,7 @@ package de.bluecolored.bluemap.sponge; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; @@ -7,9 +9,13 @@ import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import com.flowpowered.math.vector.Vector2i; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; import de.bluecolored.bluemap.core.logger.Logger; @@ -151,4 +157,77 @@ public class RenderManager { return running; } + public void writeState(DataOutputStream out) throws IOException { + //prepare renderTickets + ListMultimap tileMap = MultimapBuilder.hashKeys().arrayListValues().build(); + synchronized (renderTickets) { + for (RenderTicket ticket : renderTickets) { + tileMap.put(ticket.getMapType(), ticket.getTile()); + } + } + + //write renderTickets + Set maps = tileMap.keySet(); + out.writeInt(maps.size()); + for (MapType map : maps) { + List tiles = tileMap.get(map); + + out.writeUTF(map.getId()); + out.writeInt(tiles.size()); + for (Vector2i tile : tiles) { + out.writeInt(tile.getX()); + out.writeInt(tile.getY()); + } + } + + //write tasks + synchronized (renderTasks) { + out.writeInt(renderTasks.size()); + for (RenderTask task : renderTasks) { + task.write(out); + } + } + } + + public void readState(DataInputStream in) throws IOException { + //read renderTickets + int mapCount = in.readInt(); + for (int i = 0; i < mapCount; i++) { + String mapId = in.readUTF(); + + MapType mapType = null; + for (MapType map : SpongePlugin.getInstance().getMapTypes()) { + if (map.getId().equals(mapId)) { + mapType = map; + break; + } + } + if (mapType == null) { + Logger.global.logWarning("Some render-tickets can not be loaded because the map (id: '" + mapId + "') does not exist anymore. They will be discarded."); + } + + int tileCount = in.readInt(); + List tiles = new ArrayList<>(); + for (int j = 0; j < tileCount; j++) { + int x = in.readInt(); + int y = in.readInt(); + Vector2i tile = new Vector2i(x, y); + tiles.add(tile); + } + + createTickets(mapType, tiles); + } + + //read tasks + int taskCount = in.readInt(); + for (int i = 0; i < taskCount; i++) { + try { + RenderTask task = RenderTask.read(in); + addRenderTask(task); + } catch (IOException ex) { + Logger.global.logWarning("A render-task can not be loaded. It will be discared. (Error message: " + ex.toString() + ")"); + } + } + } + } diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTask.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTask.java index 9688ed81..2d6c573c 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTask.java +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/RenderTask.java @@ -1,9 +1,14 @@ package de.bluecolored.bluemap.sponge; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Deque; +import java.util.List; import java.util.UUID; import com.flowpowered.math.vector.Vector2d; @@ -21,8 +26,6 @@ public class RenderTask { private long additionalRunTime; private int renderedTiles; - private UUID taskOwner; - public RenderTask(String name, MapType mapType) { this.uuid = UUID.randomUUID(); this.name = name; @@ -31,7 +34,6 @@ public class RenderTask { this.firstTileTime = -1; this.additionalRunTime = 0; this.renderedTiles = 0; - this.taskOwner = null; } public void optimizeQueue() { @@ -132,8 +134,54 @@ public class RenderTask { return renderTiles.isEmpty(); } - public UUID getTaskOwner() { - return taskOwner; + public void write(DataOutputStream out) throws IOException { + synchronized (renderTiles) { + pause(); + + out.writeUTF(name); + out.writeUTF(mapType.getId()); + + out.writeLong(additionalRunTime); + out.writeInt(renderedTiles); + + out.writeInt(renderTiles.size()); + for (Vector2i tile : renderTiles) { + out.writeInt(tile.getX()); + out.writeInt(tile.getY()); + } + } + } + + public static RenderTask read(DataInputStream in) throws IOException { + String name = in.readUTF(); + String mapId = in.readUTF(); + + MapType mapType = null; + for (MapType map : SpongePlugin.getInstance().getMapTypes()) { + if (map.getId().equals(mapId)) { + mapType = map; + break; + } + } + if (mapType == null) throw new IOException("Map type with id '" + mapId + "' does not exist!"); + + RenderTask task = new RenderTask(name, mapType); + + task.additionalRunTime = in.readLong(); + task.renderedTiles = in.readInt(); + + int tileCount = in.readInt(); + List tiles = new ArrayList<>(); + for (int i = 0; i < tileCount; i++) { + int x = in.readInt(); + int y = in.readInt(); + Vector2i tile = new Vector2i(x, y); + tiles.add(tile); + } + + task.addTiles(tiles); + + return task; } } diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java index 38d81e51..2790169d 100644 --- a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java @@ -24,13 +24,21 @@ */ package de.bluecolored.bluemap.sponge; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import javax.inject.Inject; @@ -42,15 +50,19 @@ import org.spongepowered.api.event.game.GameReloadEvent; import org.spongepowered.api.event.game.state.GameStartingServerEvent; import org.spongepowered.api.event.game.state.GameStoppingEvent; import org.spongepowered.api.plugin.Plugin; +import org.spongepowered.api.plugin.PluginContainer; import org.spongepowered.api.scheduler.SpongeExecutorService; +import org.spongepowered.api.util.Tristate; import com.flowpowered.math.vector.Vector2i; import com.google.common.collect.Lists; +import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.config.ConfigurationFile; import de.bluecolored.bluemap.core.config.ConfigurationFile.MapConfig; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.MCAWorld; +import de.bluecolored.bluemap.core.metrics.Metrics; import de.bluecolored.bluemap.core.render.TileRenderer; import de.bluecolored.bluemap.core.render.hires.HiresModelManager; import de.bluecolored.bluemap.core.render.lowres.LowresModelManager; @@ -74,7 +86,7 @@ public class SpongePlugin { public static final String PLUGIN_ID = "bluemap"; public static final String PLUGIN_NAME = "BlueMap"; - public static final String PLUGIN_VERSION = "0.0.0"; + public static final String PLUGIN_VERSION = BlueMap.VERSION; private static SpongePlugin instance; @@ -190,6 +202,20 @@ public class SpongePlugin { renderManager = new RenderManager(config.getRenderThreadCount()); renderManager.start(); + //load render-manager state + try { + File saveFile = configurationDir.resolve("rmstate").toFile(); + saveFile.getParentFile().mkdirs(); + if (saveFile.exists()) { + try (DataInputStream in = new DataInputStream(new GZIPInputStream(new FileInputStream(saveFile)))) { + renderManager.readState(in); + } + } + saveFile.delete(); + } catch (IOException ex) { + Logger.global.logError("Failed to load render-manager state!", ex); + } + //start map updater updateHandler = new MapUpdateHandler(); @@ -217,6 +243,21 @@ public class SpongePlugin { webServer.start(); } + //metrics + Sponge.getScheduler().createTaskBuilder() + .async() + .delay(0, TimeUnit.MINUTES) + .interval(30, TimeUnit.MINUTES) + .execute(() -> { + Optional plugin = Sponge.getPluginManager().fromInstance(this); + if (!plugin.isPresent()) return; + + Tristate metricsEnabled = Sponge.getMetricsConfigManager().getCollectionState(plugin.get()); + if (metricsEnabled == Tristate.UNDEFINED) metricsEnabled = Sponge.getMetricsConfigManager().getGlobalCollectionState(); + if (metricsEnabled == Tristate.TRUE) Metrics.sendReport("Sponge"); + }) + .submit(this); + loaded = true; } @@ -228,7 +269,6 @@ public class SpongePlugin { //unregister listeners if (updateHandler != null) Sponge.getEventManager().unregisterListeners(updateHandler); - updateHandler = null; //unregister commands Sponge.getCommandManager().getOwnedBy(this).forEach(Sponge.getCommandManager()::removeMapping); @@ -236,6 +276,23 @@ public class SpongePlugin { //stop scheduled tasks Sponge.getScheduler().getScheduledTasks(this).forEach(t -> t.cancel()); + //save render-manager state + if (updateHandler != null) updateHandler.flushTileBuffer(); //first write all buffered tiles to the render manager to save them too + if (renderManager != null) { + try { + File saveFile = configurationDir.resolve("rmstate").toFile(); + saveFile.getParentFile().mkdirs(); + if (saveFile.exists()) saveFile.delete(); + saveFile.createNewFile(); + + try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) { + renderManager.writeState(out); + } + } catch (IOException ex) { + Logger.global.logError("Failed to save render-manager state!", ex); + } + } + //save renders for (MapType map : maps.values()) { map.getTileRenderer().save(); @@ -244,13 +301,14 @@ public class SpongePlugin { //clear resources and configs renderManager = null; webServer = null; + updateHandler = null; resourcePack = null; config = null; maps.clear(); worlds.clear(); loaded = false; - } + } public synchronized void reload() throws IOException, NoSuchResourceException { unload();