From 56549fe10a72001b94210d914c82f80660b8e405 Mon Sep 17 00:00:00 2001 From: Dylan Garvis Date: Wed, 28 Jan 2026 00:05:29 -0500 Subject: [PATCH] feat: add per-player placement and crafting metrics --- README.md | 2 + grafana-dashboard.json | 94 ++++++++++++++++--- .../spigot/listeners/BlockPlaceListener.java | 1 + .../spigot/listeners/CraftListener.java | 4 + .../spigot/metrics/MetricsRegistry.java | 22 +++++ 5 files changed, 108 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b987271..43ea09f 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,9 @@ Some metrics depend on server implementations (TPS/MSPT, player ping, tile entit | spigot_blocks_broken_total | counter | material | Blocks broken by material | | spigot_blocks_broken_by_player_total | counter | player,material | Blocks broken by player and material | | spigot_blocks_placed_total | counter | material | Blocks placed by material | +| spigot_blocks_placed_by_player_total | counter | player,material | Blocks placed by player and material | | spigot_items_crafted_total | counter | material | Items crafted by material | +| spigot_items_crafted_by_player_total | counter | player,material | Items crafted by player and material | | spigot_entities_killed_total | counter | entity,killer | Entities killed by player | | spigot_player_deaths_total | counter | cause | Player deaths by cause | | spigot_player_logins_failed_total | counter | reason | Failed player logins by reason | diff --git a/grafana-dashboard.json b/grafana-dashboard.json index f1b0dfe..1857b2e 100644 --- a/grafana-dashboard.json +++ b/grafana-dashboard.json @@ -491,13 +491,77 @@ "title": "Block Breaks by Player (Top 10)", "type": "table" }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 35 + }, + "id": 29, + "options": { + "showHeader": true + }, + "targets": [ + { + "expr": "topk(10, sum by (player, material) (rate(spigot_blocks_placed_by_player_total{instance=~\"$server\"}[5m])))", + "format": "table", + "legendFormat": "{{player}} / {{material}}", + "refId": "A" + } + ], + "title": "Block Placements by Player (Top 10)", + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 35 + }, + "id": 30, + "options": { + "showHeader": true + }, + "targets": [ + { + "expr": "topk(10, sum by (player, material) (rate(spigot_items_crafted_by_player_total{instance=~\"$server\"}[5m])))", + "format": "table", + "legendFormat": "{{player}} / {{material}}", + "refId": "A" + } + ], + "title": "Crafting by Player (Top 10)", + "type": "table" + }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 35 + "y": 44 }, "id": 26, "panels": [], @@ -519,7 +583,7 @@ "h": 9, "w": 12, "x": 0, - "y": 36 + "y": 45 }, "id": 27, "options": { @@ -561,7 +625,7 @@ "h": 9, "w": 12, "x": 12, - "y": 36 + "y": 45 }, "id": 28, "options": { @@ -586,7 +650,7 @@ "h": 1, "w": 24, "x": 0, - "y": 45 + "y": 54 }, "id": 14, "panels": [], @@ -609,7 +673,7 @@ "h": 6, "w": 6, "x": 0, - "y": 46 + "y": 55 }, "id": 15, "options": { @@ -650,7 +714,7 @@ "h": 6, "w": 6, "x": 6, - "y": 46 + "y": 55 }, "id": 16, "options": { @@ -691,7 +755,7 @@ "h": 6, "w": 6, "x": 12, - "y": 46 + "y": 55 }, "id": 17, "options": { @@ -732,7 +796,7 @@ "h": 6, "w": 6, "x": 18, - "y": 46 + "y": 55 }, "id": 18, "options": { @@ -763,7 +827,7 @@ "h": 1, "w": 24, "x": 0, - "y": 52 + "y": 61 }, "id": 19, "panels": [], @@ -785,7 +849,7 @@ "h": 9, "w": 12, "x": 0, - "y": 53 + "y": 62 }, "id": 20, "options": { @@ -827,7 +891,7 @@ "h": 9, "w": 12, "x": 12, - "y": 53 + "y": 62 }, "id": 21, "options": { @@ -850,7 +914,7 @@ "h": 1, "w": 24, "x": 0, - "y": 62 + "y": 71 }, "id": 22, "panels": [], @@ -872,7 +936,7 @@ "h": 8, "w": 12, "x": 0, - "y": 63 + "y": 72 }, "id": 23, "options": { @@ -912,7 +976,7 @@ "h": 8, "w": 6, "x": 12, - "y": 63 + "y": 72 }, "id": 24, "options": { @@ -952,7 +1016,7 @@ "h": 8, "w": 6, "x": 18, - "y": 63 + "y": 72 }, "id": 25, "options": { diff --git a/src/main/java/com/prometheus/spigot/listeners/BlockPlaceListener.java b/src/main/java/com/prometheus/spigot/listeners/BlockPlaceListener.java index b09175f..31a907e 100644 --- a/src/main/java/com/prometheus/spigot/listeners/BlockPlaceListener.java +++ b/src/main/java/com/prometheus/spigot/listeners/BlockPlaceListener.java @@ -15,5 +15,6 @@ public final class BlockPlaceListener implements Listener { @EventHandler public void onBlockPlace(BlockPlaceEvent event) { registry.incrementBlocksPlaced(event.getBlockPlaced().getType()); + registry.incrementBlocksPlaced(event.getBlockPlaced().getType(), event.getPlayer().getName()); } } diff --git a/src/main/java/com/prometheus/spigot/listeners/CraftListener.java b/src/main/java/com/prometheus/spigot/listeners/CraftListener.java index dfdf80e..cef9c70 100644 --- a/src/main/java/com/prometheus/spigot/listeners/CraftListener.java +++ b/src/main/java/com/prometheus/spigot/listeners/CraftListener.java @@ -2,6 +2,7 @@ package com.prometheus.spigot.listeners; import com.prometheus.spigot.metrics.MetricsRegistry; import org.bukkit.Material; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.CraftItemEvent; @@ -21,5 +22,8 @@ public final class CraftListener implements Listener { return; } registry.incrementItemsCrafted(result.getType()); + if (event.getWhoClicked() instanceof Player player) { + registry.incrementItemsCrafted(result.getType(), player.getName()); + } } } diff --git a/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java b/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java index 47fd8e0..83fba43 100644 --- a/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java +++ b/src/main/java/com/prometheus/spigot/metrics/MetricsRegistry.java @@ -47,11 +47,13 @@ public final class MetricsRegistry { private final ConcurrentHashMap blocksPlacedByMaterial = new ConcurrentHashMap<>(); private final ConcurrentHashMap blocksBrokenByMaterial = new ConcurrentHashMap<>(); private final ConcurrentHashMap blocksBrokenByPlayerMaterial = new ConcurrentHashMap<>(); + private final ConcurrentHashMap blocksPlacedByPlayerMaterial = new ConcurrentHashMap<>(); private final ConcurrentHashMap deathsByCause = new ConcurrentHashMap<>(); private final ConcurrentHashMap loginFailuresByReason = new ConcurrentHashMap<>(); private final ConcurrentHashMap commandsBlocked = new ConcurrentHashMap<>(); private final ConcurrentHashMap commandsExecuted = new ConcurrentHashMap<>(); private final ConcurrentHashMap itemsCraftedByMaterial = new ConcurrentHashMap<>(); + private final ConcurrentHashMap itemsCraftedByPlayerMaterial = new ConcurrentHashMap<>(); private final ConcurrentHashMap entityKills = new ConcurrentHashMap<>(); private final ConcurrentHashMap playerMovementsByPlayer = new ConcurrentHashMap<>(); @@ -106,6 +108,14 @@ public final class MetricsRegistry { incrementLabeledCounter(blocksPlacedByMaterial, material.name()); } + public void incrementBlocksPlaced(Material material, String player) { + String normalizedMaterial = normalizeLabel(material.name()); + String normalizedPlayer = normalizeLabel(player); + blocksPlacedByPlayerMaterial + .computeIfAbsent(new LabelPair(normalizedPlayer, normalizedMaterial), key -> new LongAdder()) + .increment(); + } + public void incrementPlayerDeath(EntityDamageEvent.DamageCause cause) { incrementLabeledCounter(deathsByCause, cause.name()); } @@ -148,6 +158,14 @@ public final class MetricsRegistry { incrementLabeledCounter(itemsCraftedByMaterial, material.name()); } + public void incrementItemsCrafted(Material material, String player) { + String normalizedMaterial = normalizeLabel(material.name()); + String normalizedPlayer = normalizeLabel(player); + itemsCraftedByPlayerMaterial + .computeIfAbsent(new LabelPair(normalizedPlayer, normalizedMaterial), key -> new LongAdder()) + .increment(); + } + public void incrementEntityKill(EntityType entityType, String killer) { String normalizedEntity = normalizeLabel(entityType.name()); String normalizedKiller = normalizeLabel(killer); @@ -202,11 +220,15 @@ public final class MetricsRegistry { appendLabeledCounterAdder(builder, "spigot_blocks_broken_by_player_total", "Blocks broken by player and material", "player", "material", blocksBrokenByPlayerMaterial); appendLabeledCounterAdder(builder, "spigot_blocks_placed_total", "Blocks placed by material", "material", blocksPlacedByMaterial); + appendLabeledCounterAdder(builder, "spigot_blocks_placed_by_player_total", "Blocks placed by player and material", "player", + "material", blocksPlacedByPlayerMaterial); appendLabeledCounterAdder(builder, "spigot_player_deaths_total", "Player deaths by cause", "cause", deathsByCause); appendLabeledCounterAdder(builder, "spigot_player_logins_failed_total", "Failed player logins by reason", "reason", loginFailuresByReason); appendLabeledCounterAdder(builder, "spigot_commands_blocked_total", "Blocked commands by name", "command", commandsBlocked); appendLabeledCounterAdder(builder, "spigot_commands_executed_total", "Commands executed by source", "command", "source", commandsExecuted); appendLabeledCounterAdder(builder, "spigot_items_crafted_total", "Items crafted by material", "material", itemsCraftedByMaterial); + appendLabeledCounterAdder(builder, "spigot_items_crafted_by_player_total", "Items crafted by player and material", "player", + "material", itemsCraftedByPlayerMaterial); appendLabeledCounterAdder(builder, "spigot_entities_killed_total", "Entities killed by player", "entity", "killer", entityKills); appendLabeledCounterAdder(builder, "spigot_player_movements_by_player_total", "Player movements by player", "player", playerMovementsByPlayer);