├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── build.yml │ └── releaseBuild.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── bukkit ├── build.gradle └── src │ └── main │ ├── java │ └── me │ │ └── diced │ │ └── serverstats │ │ └── bukkit │ │ ├── BukkitMetadata.java │ │ ├── BukkitMetricsManager.java │ │ ├── BukkitMetricsManagerProtocolLib.java │ │ ├── BukkitPackets.java │ │ ├── BukkitScheduler.java │ │ ├── BukkitServerStats.java │ │ └── command │ │ ├── BukkitCommandExecutor.java │ │ ├── BukkitCompletionsManager.java │ │ └── BukkitContext.java │ └── resources │ └── plugin.yml ├── bungee ├── build.gradle └── src │ └── main │ ├── java │ └── me │ │ └── diced │ │ └── serverstats │ │ └── bungee │ │ ├── BungeeMetadata.java │ │ ├── BungeeMetricsManager.java │ │ ├── BungeeScheduler.java │ │ ├── BungeeServerStats.java │ │ └── command │ │ ├── BungeeCommandExecutor.java │ │ ├── BungeeCompletionsManager.java │ │ └── BungeeContext.java │ └── resources │ └── bungee.yml ├── common ├── build.gradle └── src │ └── main │ └── java │ └── me │ └── diced │ └── serverstats │ └── common │ ├── command │ ├── Command.java │ ├── CommandExecutor.java │ ├── CompletionsManager.java │ └── Context.java │ ├── commands │ ├── GetCommand.java │ ├── HelpCommand.java │ ├── PushCommand.java │ └── ToggleCommand.java │ ├── config │ ├── ConfigLoader.java │ ├── ServerStatsConfig.java │ ├── ServerStatsLogsConfig.java │ ├── ServerStatsPushableConfig.java │ └── ServerStatsWebServerConfig.java │ ├── plugin │ ├── LogWrapper.java │ ├── ServerStats.java │ ├── ServerStatsMetadata.java │ ├── ServerStatsPlatform.java │ ├── ServerStatsType.java │ └── Util.java │ ├── prometheus │ ├── Metric.java │ ├── MetricsExporter.java │ ├── MetricsHttpServer.java │ ├── MetricsManager.java │ └── metrics │ │ ├── jvm │ │ ├── CPU.java │ │ ├── FreeMemory.java │ │ ├── GC.java │ │ ├── MaxMemory.java │ │ ├── Threads.java │ │ ├── TotalMemory.java │ │ └── Uptime.java │ │ ├── server │ │ ├── EntityCount.java │ │ ├── LoadedChunks.java │ │ ├── MSPT.java │ │ ├── PacketRX.java │ │ ├── PacketTX.java │ │ ├── PlayerCount.java │ │ └── TPS.java │ │ └── world │ │ ├── DiskSpacePerWorld.java │ │ ├── EntityCountPerWorld.java │ │ └── LoadedChunksPerWorld.java │ └── scheduler │ ├── Scheduler.java │ ├── Task.java │ └── ThreadScheduler.java ├── example-grafana-dashboard.json ├── example-grafana-dashboard.png ├── fabric ├── build.gradle └── src │ └── main │ ├── java │ └── me │ │ └── diced │ │ └── serverstats │ │ └── fabric │ │ ├── FabricMetadata.java │ │ ├── FabricMetricsManager.java │ │ ├── FabricServerStats.java │ │ └── command │ │ ├── FabricCommandExecutor.java │ │ ├── FabricCompletionsManager.java │ │ └── FabricContext.java │ └── resources │ └── fabric.mod.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts ├── build.sh └── clean.sh ├── settings.gradle └── velocity ├── build.gradle └── src └── main └── java └── me └── diced └── serverstats └── velocity ├── VelocityMetadata.java ├── VelocityMetricsManager.java ├── VelocityScheduler.java ├── VelocityServerStats.java └── command ├── VelocityCommandExecutor.java ├── VelocityCompletionsManager.java └── VelocityContext.java /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Bug report 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Please complete the following information):** 27 | - OS: [e.g. Linux, Windows, macOS] 28 | - Server Software: [e.g. Bukkit, Paper, Spigot, Fabric, Bungee] 29 | - ServerStats Version: [e.g. 1.2.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build ServerStats 2 | on: 3 | push: 4 | paths: 5 | - 'bukkit/**' 6 | - 'bungee/**' 7 | - 'fabric/**' 8 | - 'common/**' 9 | - '.github/**' 10 | - 'gradle*' 11 | branches: [ trunk ] 12 | pull_request: 13 | paths: 14 | - 'bukkit/**' 15 | - 'bungee/**' 16 | - 'fabric/**' 17 | - 'common/**' 18 | - '.github/**' 19 | branches: [ trunk ] 20 | 21 | jobs: 22 | build: 23 | outputs: 24 | version: ${{ steps.version.outputs.version }} 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: gradle/wrapper-validation-action@v1 29 | 30 | - name: Get Version 31 | id: version 32 | run: echo "::set-output name=version::$(cat gradle.properties | awk '/mod_version/ && sub(/^.{14}/,"",$0)')" 33 | 34 | - name: Set up JDK 35 | uses: actions/setup-java@v2 36 | with: 37 | distribution: 'adopt' 38 | java-version: '17' 39 | - name: Cache Gradle packages 40 | uses: actions/cache@v2 41 | with: 42 | path: ~/.gradle 43 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 44 | 45 | - name: Make build.sh executable 46 | run: chmod +x ./scripts/build.sh 47 | 48 | - name: Build 49 | run: ./scripts/build.sh 50 | 51 | - uses: actions/upload-artifact@v2 52 | with: 53 | name: build_artifacts 54 | path: builds 55 | release: 56 | needs: build 57 | if: github.event_name != 'pull_request' 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: actions/checkout@v2 61 | with: 62 | persist-credentials: false 63 | fetch-depth: 0 64 | 65 | - name: Setup Git 66 | run: | 67 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 68 | git config --global user.name "github-actions[bot]" 69 | mkdir build 70 | - name: Download artifacts 71 | uses: actions/download-artifact@v2 72 | with: 73 | path: build 74 | 75 | - name: Setup git upload 76 | run: | 77 | cd ./build && ls 78 | git init 79 | git remote add origin https://diced:${{ secrets.GITHUB_TOKEN }}@github.com/diced/ServerStats.git 80 | mv build_artifacts ../build_artifacts 81 | - name: Setup Git branch if dev 82 | if: ${{ github.ref == 'refs/heads/trunk' }} 83 | run: | 84 | cd ./build 85 | git pull origin dev-builds 86 | git checkout dev-builds 87 | mv ../build_artifacts/* build_artifacts 88 | - name: Release dev build 89 | if: ${{ github.ref == 'refs/heads/trunk' }} 90 | run: | 91 | cd ./build 92 | git add --all 93 | if [[ $(git diff --stat --staged) != '' ]]; then 94 | git commit -m "Deploy [${{ needs.build.outputs.version }}] [${{ github.sha }}]" 95 | git push --force --set-upstream origin dev-builds 96 | else 97 | echo "clean, not committing" 98 | fi -------------------------------------------------------------------------------- /.github/workflows/releaseBuild.yml: -------------------------------------------------------------------------------- 1 | name: Build & Release ServerStats 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | 7 | jobs: 8 | release: 9 | outputs: 10 | version: ${{ steps.version.outputs.version }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Get Version 16 | id: version 17 | run: echo "::set-output name=version::$(cat gradle.properties | awk '/mod_version/ && sub(/^.{14}/,"",$0)')" 18 | 19 | - name: Set up JDK 20 | uses: actions/setup-java@v2 21 | with: 22 | distribution: 'adopt' 23 | java-version: '17' 24 | 25 | - name: Make build.sh executable 26 | run: chmod +x ./scripts/build.sh 27 | 28 | - name: Build 29 | run: ./scripts/build.sh 30 | 31 | - uses: actions/upload-artifact@v2 32 | with: 33 | name: build_artifacts 34 | path: builds 35 | 36 | - name: Release 37 | uses: softprops/action-gh-release@v1 38 | if: startsWith(github.ref, 'refs/tags/') 39 | with: 40 | files: | 41 | builds/ServerStats-Fabric-${{ steps.version.outputs.version }}.jar 42 | builds/ServerStats-Bukkit-${{ steps.version.outputs.version }}.jar 43 | builds/ServerStats-Bungee-${{ steps.version.outputs.version }}.jar 44 | builds/ServerStats-Velocity-${{ steps.version.outputs.version }}.jar 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | upload-artifacts: 48 | needs: release 49 | if: github.event_name != 'pull_request' 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v2 53 | with: 54 | persist-credentials: false 55 | fetch-depth: 0 56 | 57 | - name: Setup Git 58 | run: | 59 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 60 | git config --global user.name "github-actions[bot]" 61 | mkdir build 62 | - name: Download artifacts 63 | uses: actions/download-artifact@v2 64 | with: 65 | path: build 66 | 67 | - name: Setup git upload 68 | run: | 69 | cd ./build && ls 70 | git init 71 | git remote add origin https://diced:${{ secrets.GITHUB_TOKEN }}@github.com/diced/ServerStats.git 72 | mv build_artifacts ../build_artifacts 73 | - name: Setup Git branch if stable 74 | run: | 75 | cd ./build 76 | git pull origin builds 77 | git checkout builds 78 | mv ../build_artifacts/* build_artifacts 79 | - name: Release stable build 80 | run: | 81 | cd ./build 82 | git add --all 83 | if [[ $(git diff --stat --staged) != '' ]]; then 84 | git commit -m "Deploy [${{ needs.release.outputs.version }}] [${{ github.sha }}]" 85 | git push --force --set-upstream origin builds 86 | else 87 | echo "clean, not committing" 88 | fi -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle & IntelliJ ### 2 | .gradle/ 3 | /.idea/ 4 | build/ 5 | out/ 6 | run/ 7 | *.iml 8 | .idea_modules/ 9 | 10 | 11 | ### Eclipse ### 12 | .metadata 13 | bin/ 14 | tmp/ 15 | *.tmp 16 | *.bak 17 | *.swp 18 | *~.nib 19 | local.properties 20 | .settings/ 21 | .loadpath 22 | .recommenders 23 | .externalToolBuilders/ 24 | *.launch 25 | .factorypath 26 | .recommenders/ 27 | .apt_generated/ 28 | .project 29 | .classpath 30 | 31 | 32 | ### Linux ### 33 | *~ 34 | .fuse_hidden* 35 | .directory 36 | .Trash-* 37 | .nfs* 38 | 39 | 40 | ### macOS ### 41 | .DS_Store 42 | .AppleDouble 43 | .LSOverride 44 | Icon 45 | ._* 46 | .DocumentRevisions-V100 47 | .fseventsd 48 | .Spotlight-V100 49 | .TemporaryItems 50 | .Trashes 51 | .VolumeIcon.icns 52 | .com.apple.timemachine.donotpresent 53 | .AppleDB 54 | .AppleDesktop 55 | Network Trash Folder 56 | Temporary Items 57 | .apdisk 58 | 59 | 60 | ### NetBeans ### 61 | nbproject/private/ 62 | build/ 63 | nbbuild/ 64 | dist/ 65 | nbdist/ 66 | .nb-gradle/ 67 | 68 | 69 | ### Windows ### 70 | # Windows thumbnail cache files 71 | Thumbs.db 72 | ehthumbs.db 73 | ehthumbs_vista.db 74 | *.stackdump 75 | [Dd]esktop.ini 76 | $RECYCLE.BIN/ 77 | *.lnk 78 | 79 | # serverstats combined build 80 | builds/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 dicedtomato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServerStats 2 | Visualize your Minecraft server statistics in realtime for Minecraft 1.8+ (*Only 1.17+ for Fabric*) 3 | 4 | Want to learn more? Visit [serverstats.diced.me](https://serverstats.diced.me) for the latest downloads and documentation. 5 | 6 | ![Grafana Dashboard Example](https://raw.githubusercontent.com/diced/serverstats/trunk/example-grafana-dashboard.png) 7 | 8 | ## Downloading 9 | There's multiple places where you can download ServerStats 10 | 11 | ### Stable Builds 12 | * **`builds`** branch contains builds from every stable tag release 13 | * **[Website](https://serverstats.diced.me/download)** contains the builds from stable releases 14 | * **[GitHub Releases](https://github.com/diced/ServerStats/releases)** contains the builds from stable releases 15 | 16 | (*[dl.diced.me/serverstats/latest](https://dl.diced.me/serverstats/latest)*) 17 | 18 | ### Development Builds (Bleeding Edge) 19 | * **`dev-builds`** branch contains builds from every commit 20 | * **GitHub Actions Artifacts** zip file containing jars from commit actions 21 | 22 | ## Features 23 | * Easy: setup ServerStats in minutes 24 | * Prometheus: export stats to prometheus 25 | * Grafana: visualize your stats in Grafana using our provided dashboard 26 | * Cross server compatible: works with proxy servers, Fabric and Bukkit compatible servers (Spigot, Paper, etc) 27 | 28 | ## Metrics 29 | This is a list of all the metrics provides. The code block has the config property. 30 | [Docs Link to Configuration](https://serverstats.diced.me/docs/config#pushable) 31 | ### Player Count `player-count` 32 | The online player count 33 | ### Free Memory `free-memory` 34 | Free memory in JVM 35 | ### Max Memory `max-memory` 36 | Max memory in JVM 37 | ### Total Memory `total-memory` 38 | Total memory allocated to JVM 39 | ### TPS `tps` 40 | Ticks per-second (usually 20) 41 | ### MSPT `mspt` 42 | Milliseconds per-tick (usually below 50ms for good perf) 43 | ### CPU % `cpu` 44 | CPU Percentage 45 | ### Loaded Chunks `loaded-chunks` 46 | Loaded chunks in each world 47 | ### Entity Count `entity-count` 48 | Entity count in each world 49 | ### Disk Space used `disk-space` 50 | Disk space that each world is using 51 | ### Packets Sent/Read `packets` 52 | Packets that are being sent to clients and being read from clients 53 | ### Garbage Collection Time `gc` 54 | The time it took to do the last GC 55 | ### Thread Count `threads` 56 | The amount of JVM threads 57 | ### Uptime `uptime` 58 | The uptime of the server 59 | 60 | ## Compiling 61 | You must be using Java 16+ as Fabric/Minecraft 1.17 requires it, or go into the directory you want and build using a different JDK. 62 | ```shell 63 | git clone https://github.com/diced/ServerStats 64 | cd ServerStats 65 | ./build.sh # custom build shell script that builds all modules and gives all jars in builds/ 66 | ``` 67 | 68 | ## Project Layout 69 | ServerStats utilizes mono-repos/modules to have different server types in one repo. 70 | 71 | * **Common** - Common modules with interfaces and abstract classes that bukkit/bungee/fabric/velocity use 72 | * **Bukkit/Bungee/Fabric/Velocity** - Uses the common module's interfaces to implement a platform specific plugin 73 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | 6 | dependencies { 7 | classpath 'gradle.plugin.com.github.jengelman.gradle.plugins:shadow:7.0.0' 8 | } 9 | } 10 | 11 | group = project.mod_group 12 | version = project.mod_version 13 | 14 | 15 | subprojects { 16 | apply plugin: 'java' 17 | apply plugin: 'maven-publish' 18 | 19 | group = project.group 20 | version = project.version 21 | 22 | repositories { 23 | mavenCentral() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bukkit/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.github.johnrengelman.shadow' 3 | } 4 | 5 | archivesBaseName = 'ServerStats-Bukkit' 6 | version = project.mod_version 7 | group = project.mod_group + '.bukkit' 8 | 9 | repositories { 10 | maven { url 'https://papermc.io/repo/repository/maven-public/' } 11 | maven { url "https://repo.dmulloy2.net/repository/public/" } 12 | } 13 | 14 | dependencies { 15 | implementation project(':common') 16 | 17 | compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT' 18 | 19 | compileOnly 'com.comphenix.protocol:ProtocolLib:4.7.0' 20 | implementation 'org.slf4j:slf4j-jdk14:1.7.30' 21 | implementation 'net.kyori:adventure-api:4.8.1' 22 | implementation 'net.kyori:adventure-platform-bukkit:4.0.0-SNAPSHOT' 23 | } 24 | 25 | processResources { 26 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 27 | from(sourceSets.main.resources.srcDirs) { 28 | include 'plugin.yml' 29 | expand 'version': project.version 30 | } 31 | } 32 | 33 | shadowJar { 34 | archiveClassifier.set(null) 35 | minimize() 36 | 37 | relocate 'org.slf4j', "${rootProject.group}.lib.org.slf4j" 38 | relocate 'org.spongepowered.configurate', "${rootProject.group}.lib.org.spongepowered.configurate" 39 | relocate 'org.checkerframework', "${rootProject.group}.lib.org.checkerframework" 40 | relocate 'net.kyori.adventure', "${rootProject.group}.lib.adventure" 41 | } 42 | 43 | tasks.build.dependsOn tasks.shadowJar -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/BukkitMetadata.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 4 | import me.diced.serverstats.common.plugin.ServerStatsType; 5 | import org.bukkit.plugin.PluginDescriptionFile; 6 | 7 | public class BukkitMetadata implements ServerStatsMetadata { 8 | private final PluginDescriptionFile meta; 9 | 10 | public BukkitMetadata(PluginDescriptionFile meta) { 11 | this.meta = meta; 12 | } 13 | 14 | @Override 15 | public ServerStatsType getType() { 16 | return ServerStatsType.BUNGEE; 17 | } 18 | 19 | @Override 20 | public String getVersion() { 21 | return this.meta.getVersion(); 22 | } 23 | 24 | @Override 25 | public String getAuthor() { 26 | return this.meta.getAuthors().get(0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/BukkitMetricsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit; 2 | 3 | import me.diced.serverstats.common.prometheus.MetricsManager; 4 | 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.TreeMap; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | public class BukkitMetricsManager extends MetricsManager { 11 | private final BukkitServerStats platform; 12 | 13 | public BukkitMetricsManager(BukkitServerStats platform) { 14 | super(platform.getServerStats()); 15 | this.platform = platform; 16 | } 17 | 18 | @Override 19 | public int getPlayerCount() { 20 | return this.platform.getServer().getOnlinePlayers().size(); 21 | } 22 | 23 | @Override 24 | public double getMspt() { 25 | return this.platform.getServer().getAverageTickTime(); 26 | } 27 | 28 | @Override 29 | public double getTps() { 30 | return this.platform.getServer().getTPS()[0]; 31 | } 32 | 33 | @Override 34 | public int getLoadedChunks() { 35 | AtomicInteger loadedChunks = new AtomicInteger(); 36 | 37 | this.platform.getServer().getWorlds().forEach(w -> loadedChunks.addAndGet(w.getChunkCount())); 38 | 39 | return loadedChunks.intValue(); 40 | } 41 | 42 | @Override 43 | public int getEntityCount() { 44 | AtomicInteger entityCount = new AtomicInteger(); 45 | 46 | this.platform.getServer().getWorlds().forEach(w -> w.getEntityCount()); 47 | 48 | return entityCount.intValue(); 49 | } 50 | 51 | @Override 52 | public TreeMap getLoadedChunksPerWorld() { 53 | TreeMap worlds = new TreeMap<>(); 54 | 55 | this.platform.getServer().getWorlds().forEach(w -> worlds.put(w.getName(), w.getLoadedChunks().length)); 56 | return worlds; 57 | } 58 | 59 | @Override 60 | public TreeMap getEntityCountPerWorld() { 61 | TreeMap entities = new TreeMap<>(); 62 | 63 | this.platform.getServer().getWorlds().forEach(w -> entities.put(w.getName(), w.getEntityCount())); 64 | 65 | return entities; 66 | } 67 | 68 | @Override 69 | public TreeMap getWorldPaths() { 70 | TreeMap worlds = new TreeMap<>(); 71 | 72 | this.platform.getServer().getWorlds().forEach(w -> { 73 | String name = w.getName(); 74 | 75 | worlds.put(name, Paths.get(".", name)); 76 | }); 77 | 78 | return worlds; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/BukkitMetricsManagerProtocolLib.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit; 2 | 3 | public class BukkitMetricsManagerProtocolLib extends BukkitMetricsManager { 4 | public BukkitMetricsManagerProtocolLib(BukkitServerStats platform) { 5 | super(platform); 6 | } 7 | 8 | @Override 9 | public long getPacketRx() { 10 | return BukkitPackets.packetsRx; 11 | } 12 | 13 | @Override 14 | public long getPacketTx() { 15 | return BukkitPackets.packetsTx; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/BukkitPackets.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.ProtocolLibrary; 5 | import com.comphenix.protocol.events.*; 6 | 7 | public class BukkitPackets { 8 | private final BukkitServerStats platform; 9 | 10 | public static long packetsRx = 0; 11 | public static long packetsTx = 0; 12 | 13 | public BukkitPackets(BukkitServerStats platform) { 14 | this.platform = platform; 15 | ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter(this.platform, ListenerPriority.MONITOR, PacketType.values()) { 16 | @Override 17 | public void onPacketSending(PacketEvent event) { 18 | packetsTx++; 19 | } 20 | 21 | @Override 22 | public void onPacketReceiving(PacketEvent event) { 23 | packetsRx++; 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/BukkitScheduler.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit; 2 | 3 | import me.diced.serverstats.common.scheduler.Scheduler; 4 | import me.diced.serverstats.common.scheduler.Task; 5 | import org.bukkit.scheduler.BukkitTask; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class BukkitScheduler implements Scheduler { 10 | private final BukkitServerStats platform; 11 | private final org.bukkit.scheduler.BukkitScheduler scheduler; 12 | 13 | public BukkitScheduler(BukkitServerStats platform) { 14 | this.platform = platform; 15 | this.scheduler = platform.getServer().getScheduler(); 16 | } 17 | 18 | @Override 19 | public Task scheduleRepeatingTask(Runnable task, long interval, TimeUnit unit) { 20 | long time = 0; 21 | 22 | if (unit == TimeUnit.MILLISECONDS) { 23 | time = (interval / 1000) * 20; 24 | } 25 | 26 | BukkitTask sct = this.scheduler.runTaskTimer(this.platform, task, 0, time); 27 | return () -> sct.cancel(); 28 | } 29 | 30 | @Override 31 | public Task schedule(Runnable task) { 32 | BukkitTask sct = this.scheduler.runTaskAsynchronously(this.platform, task); 33 | return () -> sct.cancel(); 34 | } 35 | 36 | @Override 37 | public void stop() { 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/BukkitServerStats.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit; 2 | 3 | import me.diced.serverstats.bukkit.command.BukkitCommandExecutor; 4 | import me.diced.serverstats.common.plugin.LogWrapper; 5 | import me.diced.serverstats.common.prometheus.MetricsManager; 6 | import me.diced.serverstats.common.plugin.ServerStats; 7 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 8 | import me.diced.serverstats.common.plugin.ServerStatsPlatform; 9 | import me.diced.serverstats.common.scheduler.Scheduler; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.plugin.java.JavaPlugin; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.io.IOException; 16 | import java.nio.file.Path; 17 | 18 | public class BukkitServerStats extends JavaPlugin implements ServerStatsPlatform, Listener { 19 | private ServerStats serverStats; 20 | private BukkitScheduler scheduler; 21 | private final BukkitMetadata meta = new BukkitMetadata(this.getDescription()); 22 | private MetricsManager metricsManager; 23 | private final Logger logger = LoggerFactory.getLogger("ServerStats"); 24 | 25 | @Override 26 | public final void onEnable() { 27 | try { 28 | LogWrapper logger = new LogWrapper() { 29 | private final Logger logger = LoggerFactory.getLogger("ServerStats"); 30 | 31 | @Override 32 | public void info(String msg) { 33 | this.logger.info(msg); 34 | } 35 | 36 | @Override 37 | public void error(String msg) { 38 | this.logger.error(msg); 39 | } 40 | }; 41 | 42 | this.scheduler = new BukkitScheduler(this); 43 | this.serverStats = new ServerStats(this, logger); 44 | new BukkitCommandExecutor(this); 45 | 46 | this.start(); 47 | } catch (IOException e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | 52 | @Override 53 | public final void onDisable() { 54 | this.serverStats.stop(); 55 | } 56 | 57 | @Override 58 | public Path getConfigPath() { 59 | return getDataFolder().toPath().resolve("serverstats.conf"); 60 | } 61 | 62 | @Override 63 | public ServerStatsMetadata getMetadata() { 64 | return this.meta; 65 | } 66 | 67 | @Override 68 | public ServerStats getServerStats() { 69 | return this.serverStats; 70 | } 71 | 72 | @Override 73 | public Scheduler getScheduler() { 74 | return this.scheduler; 75 | } 76 | 77 | @Override 78 | public MetricsManager getMetricsManager() { 79 | return this.metricsManager; 80 | } 81 | 82 | @Override 83 | public void start() { 84 | if (this.getServer().getPluginManager().getPlugin("ProtocolLib") != null) { 85 | this.serverStats.logger.info("Enabling Packet Metrics since ProtocolLib exists"); 86 | new BukkitPackets(this); 87 | this.metricsManager = new BukkitMetricsManagerProtocolLib(this); 88 | } else { 89 | this.serverStats.logger.info("Disabling Packet Metrics since ProtocolLib doesn't exist"); 90 | this.metricsManager = new BukkitMetricsManager(this); 91 | } 92 | this.serverStats.tasks.register(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/command/BukkitCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit.command; 2 | 3 | import me.diced.serverstats.bukkit.BukkitServerStats; 4 | import me.diced.serverstats.common.plugin.ServerStats; 5 | import me.diced.serverstats.common.command.CommandExecutor; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.PluginCommand; 9 | import org.bukkit.command.TabExecutor; 10 | import org.bukkit.event.Listener; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import static me.diced.serverstats.common.plugin.Util.tokenize; 18 | 19 | public class BukkitCommandExecutor implements CommandExecutor, TabExecutor, Listener { 20 | private final BukkitServerStats platform; 21 | 22 | public BukkitCommandExecutor(BukkitServerStats platform) { 23 | this.platform = platform; 24 | PluginCommand command = platform.getCommand("stats"); 25 | 26 | command.setExecutor(this); 27 | command.setTabCompleter(this); 28 | 29 | this.platform.getServer().getPluginManager().registerEvents(this, this.platform); 30 | } 31 | 32 | @Override 33 | public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { 34 | List arguments = tokenize(args); 35 | 36 | BukkitContext ctx = new BukkitContext(sender, this.platform); 37 | 38 | this.executeCommand(arguments, ctx); 39 | 40 | return true; 41 | } 42 | 43 | @Override 44 | public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { 45 | List res = new ArrayList<>(); 46 | BukkitContext ctx = new BukkitContext(sender, this.platform); 47 | BukkitCompletionsManager completions = new BukkitCompletionsManager(res, ctx); 48 | 49 | completions.register(); 50 | 51 | return res; 52 | } 53 | 54 | @Override 55 | public ServerStats getPlatform() { 56 | return this.platform.getServerStats(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/command/BukkitCompletionsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit.command; 2 | 3 | import me.diced.serverstats.common.command.CompletionsManager; 4 | import me.diced.serverstats.common.command.Context; 5 | 6 | import java.util.List; 7 | 8 | public class BukkitCompletionsManager extends CompletionsManager> { 9 | public BukkitCompletionsManager(List strings, Context ctx) { 10 | super(strings, ctx); 11 | } 12 | 13 | @Override 14 | public void suggest(String cmd) { 15 | this.suggester.add(cmd); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/diced/serverstats/bukkit/command/BukkitContext.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bukkit.command; 2 | 3 | import me.diced.serverstats.bukkit.BukkitServerStats; 4 | import me.diced.serverstats.common.command.Context; 5 | import net.kyori.adventure.platform.bukkit.BukkitAudiences; 6 | import net.kyori.adventure.text.Component; 7 | import org.bukkit.command.CommandSender; 8 | 9 | public class BukkitContext implements Context { 10 | private final CommandSender sender; 11 | private final BukkitAudiences audiences; 12 | 13 | public BukkitContext(CommandSender sender, BukkitServerStats plugin) { 14 | this.sender = sender; 15 | this.audiences = BukkitAudiences.create(plugin); 16 | } 17 | 18 | public void sendMessage(Component message) { 19 | this.audiences.sender(this.sender).sendMessage(message); 20 | } 21 | 22 | public boolean isOp() { 23 | return this.sender.isOp(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ServerStats 2 | main: me.diced.serverstats.bukkit.BukkitServerStats 3 | version: ${version} 4 | api-version: 1.13 5 | description: Visualize your Minecraft server statistics in realtime 6 | authors: [dicedtomato] 7 | website: https://serverstats.diced.me 8 | softdepend: [ProtocolLib] 9 | 10 | commands: 11 | stats: 12 | description: Manage Server Stats -------------------------------------------------------------------------------- /bungee/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.github.johnrengelman.shadow' 3 | } 4 | 5 | archivesBaseName = 'ServerStats-Bungee' 6 | version = project.mod_version 7 | group = project.mod_group + '.bungee' 8 | 9 | repositories { 10 | maven { 11 | name = 'sonatype' 12 | url = 'https://oss.sonatype.org/content/groups/public/' 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation project(':common') 18 | 19 | compileOnly 'net.md-5:bungeecord-api:1.16-R0.4' 20 | implementation 'net.kyori:adventure-api:4.8.1' 21 | implementation 'net.kyori:adventure-platform-bungeecord:4.0.1' 22 | } 23 | 24 | processResources { 25 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 26 | 27 | from(sourceSets.main.resources.srcDirs) { 28 | expand 'version': project.version 29 | } 30 | } 31 | 32 | shadowJar { 33 | archiveClassifier.set(null) 34 | minimize() 35 | 36 | relocate 'org.slf4j', "${rootProject.group}.lib.org.slf4j" 37 | relocate 'org.spongepowered.configurate', "${rootProject.group}.lib.org.spongepowered.configurate" 38 | relocate 'org.checkerframework', "${rootProject.group}.lib.org.checkerframework" 39 | relocate 'net.kyori.adventure', "${rootProject.group}.lib.adventure" 40 | 41 | } 42 | 43 | tasks.build.dependsOn tasks.shadowJar -------------------------------------------------------------------------------- /bungee/src/main/java/me/diced/serverstats/bungee/BungeeMetadata.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bungee; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 4 | import me.diced.serverstats.common.plugin.ServerStatsType; 5 | import net.md_5.bungee.api.plugin.PluginDescription; 6 | 7 | public class BungeeMetadata implements ServerStatsMetadata { 8 | private final PluginDescription meta; 9 | 10 | public BungeeMetadata(PluginDescription meta) { 11 | this.meta = meta; 12 | } 13 | 14 | @Override 15 | public ServerStatsType getType() { 16 | return ServerStatsType.BUNGEE; 17 | } 18 | 19 | @Override 20 | public String getVersion() { 21 | return this.meta.getVersion(); 22 | } 23 | 24 | @Override 25 | public String getAuthor() { 26 | return this.meta.getAuthor(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/diced/serverstats/bungee/BungeeMetricsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bungee; 2 | 3 | import me.diced.serverstats.common.prometheus.MetricsManager; 4 | 5 | public class BungeeMetricsManager extends MetricsManager { 6 | private final BungeeServerStats platform; 7 | 8 | public BungeeMetricsManager(BungeeServerStats platform) { 9 | super(platform.getServerStats()); 10 | this.platform = platform; 11 | } 12 | 13 | @Override 14 | public int getPlayerCount() { 15 | return this.platform.getProxy().getOnlineCount(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/diced/serverstats/bungee/BungeeScheduler.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bungee; 2 | 3 | import me.diced.serverstats.common.scheduler.Scheduler; 4 | import me.diced.serverstats.common.scheduler.Task; 5 | import net.md_5.bungee.api.scheduler.ScheduledTask; 6 | import net.md_5.bungee.api.scheduler.TaskScheduler; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class BungeeScheduler implements Scheduler { 11 | private final BungeeServerStats platform; 12 | private final TaskScheduler scheduler; 13 | public BungeeScheduler(BungeeServerStats platform) { 14 | this.platform = platform; 15 | this.scheduler = platform.getProxy().getScheduler(); 16 | } 17 | 18 | @Override 19 | public Task scheduleRepeatingTask(Runnable task, long interval, TimeUnit unit) { 20 | ScheduledTask sct = this.scheduler.schedule(this.platform, task, 0, interval, unit); 21 | return () -> sct.cancel(); 22 | } 23 | 24 | @Override 25 | public Task schedule(Runnable task) { 26 | ScheduledTask sct = this.scheduler.runAsync(this.platform, task); 27 | return () -> sct.cancel(); 28 | } 29 | 30 | @Override 31 | public void stop() { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/diced/serverstats/bungee/BungeeServerStats.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bungee; 2 | 3 | import me.diced.serverstats.bungee.command.BungeeCommandExecutor; 4 | import me.diced.serverstats.common.plugin.LogWrapper; 5 | import me.diced.serverstats.common.prometheus.MetricsManager; 6 | import me.diced.serverstats.common.plugin.ServerStats; 7 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 8 | import me.diced.serverstats.common.plugin.ServerStatsPlatform; 9 | import me.diced.serverstats.common.scheduler.Scheduler; 10 | import net.md_5.bungee.api.plugin.Listener; 11 | import net.md_5.bungee.api.plugin.Plugin; 12 | import net.md_5.bungee.api.plugin.PluginLogger; 13 | 14 | import java.io.IOException; 15 | import java.nio.file.Path; 16 | import java.util.logging.Logger; 17 | 18 | public final class BungeeServerStats extends Plugin implements ServerStatsPlatform, Listener { 19 | private ServerStats serverStats; 20 | private BungeeScheduler scheduler; 21 | private final BungeeMetadata meta = new BungeeMetadata(this.getDescription()); 22 | private BungeeMetricsManager metricsManager; 23 | 24 | @Override 25 | public void onEnable() { 26 | try { 27 | LogWrapper logger = new LogWrapper() { 28 | private final Logger logger = PluginLogger.getLogger("ServerStats"); 29 | 30 | @Override 31 | public void info(String msg) { 32 | this.logger.info(msg); 33 | } 34 | 35 | @Override 36 | public void error(String msg) { 37 | this.logger.severe(msg); 38 | } 39 | }; 40 | 41 | this.scheduler = new BungeeScheduler(this); 42 | this.serverStats = new ServerStats(this, logger); 43 | this.metricsManager = new BungeeMetricsManager(this); 44 | 45 | new BungeeCommandExecutor(this); 46 | 47 | this.getProxy().getPluginManager().registerListener(this, this); 48 | 49 | this.start(); 50 | } catch (IOException e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | 55 | @Override 56 | public void onDisable() { 57 | this.serverStats.stop(); 58 | } 59 | 60 | @Override 61 | public Path getConfigPath() { 62 | return this.getDataFolder().toPath().resolve("serverstats.conf"); 63 | } 64 | 65 | @Override 66 | public ServerStatsMetadata getMetadata() { 67 | return this.meta; 68 | } 69 | 70 | @Override 71 | public MetricsManager getMetricsManager() { 72 | return this.metricsManager; 73 | } 74 | 75 | @Override 76 | public ServerStats getServerStats() { 77 | return this.serverStats; 78 | } 79 | 80 | @Override 81 | public Scheduler getScheduler() { 82 | return this.scheduler; 83 | } 84 | 85 | @Override 86 | public void start() { 87 | this.serverStats.tasks.register(); 88 | } 89 | } -------------------------------------------------------------------------------- /bungee/src/main/java/me/diced/serverstats/bungee/command/BungeeCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bungee.command; 2 | 3 | import me.diced.serverstats.bungee.BungeeServerStats; 4 | import me.diced.serverstats.common.plugin.ServerStats; 5 | import me.diced.serverstats.common.command.CommandExecutor; 6 | import net.md_5.bungee.api.CommandSender; 7 | import net.md_5.bungee.api.plugin.Command; 8 | import net.md_5.bungee.api.plugin.TabExecutor; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static me.diced.serverstats.common.plugin.Util.tokenize; 14 | 15 | public class BungeeCommandExecutor extends Command implements CommandExecutor, TabExecutor { 16 | private final BungeeServerStats platform; 17 | 18 | public BungeeCommandExecutor(BungeeServerStats platform) { 19 | super("stats", null); 20 | 21 | this.platform = platform; 22 | this.platform.getProxy().getPluginManager().registerCommand(this.platform, this); 23 | } 24 | 25 | @Override 26 | public Iterable onTabComplete(CommandSender sender, String[] args) { 27 | List res = new ArrayList<>(); 28 | 29 | BungeeContext ctx = new BungeeContext(sender, this.platform); 30 | BungeeCompletionsManager completions = new BungeeCompletionsManager(res, ctx); 31 | 32 | completions.registerProxy(); 33 | 34 | return res; 35 | } 36 | 37 | @Override 38 | public void execute(CommandSender sender, String[] args) { 39 | List arguments = tokenize(args); 40 | 41 | BungeeContext ctx = new BungeeContext(sender, this.platform); 42 | 43 | this.executeCommand(arguments, ctx); 44 | } 45 | 46 | @Override 47 | public ServerStats getPlatform() { 48 | return this.platform.getServerStats(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/diced/serverstats/bungee/command/BungeeCompletionsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bungee.command; 2 | 3 | import me.diced.serverstats.common.command.CompletionsManager; 4 | import me.diced.serverstats.common.command.Context; 5 | 6 | import java.util.List; 7 | 8 | public class BungeeCompletionsManager extends CompletionsManager> { 9 | public BungeeCompletionsManager(List strings, Context ctx) { 10 | super(strings, ctx); 11 | } 12 | 13 | @Override 14 | public void suggest(String cmd) { 15 | this.suggester.add(cmd); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/diced/serverstats/bungee/command/BungeeContext.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.bungee.command; 2 | 3 | import me.diced.serverstats.bungee.BungeeServerStats; 4 | import me.diced.serverstats.common.command.Context; 5 | import net.kyori.adventure.platform.bungeecord.BungeeAudiences; 6 | import net.kyori.adventure.text.Component; 7 | import net.md_5.bungee.api.CommandSender; 8 | 9 | public class BungeeContext implements Context { 10 | private final BungeeServerStats plugin; 11 | private final CommandSender sender; 12 | private final BungeeAudiences audiences; 13 | 14 | public BungeeContext(CommandSender sender, BungeeServerStats plugin) { 15 | this.sender = sender; 16 | this.plugin = plugin; 17 | this.audiences = BungeeAudiences.create(this.plugin); 18 | } 19 | 20 | public void sendMessage(Component message) { 21 | this.audiences.sender(this.sender).sendMessage(message); 22 | } 23 | 24 | public boolean isOp() { 25 | return this.plugin.getProxy().getConsole().equals(this.sender); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bungee/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: ServerStats 2 | version: ${version} 3 | main: me.diced.serverstats.bungee.BungeeServerStats 4 | author: dicedtomato 5 | description: Visualize your Minecraft server statistics in realtime 6 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.spongepowered:configurate-hocon:4.1.2' 3 | implementation 'org.apache.logging.log4j:log4j-core:2.15.0' 4 | implementation 'net.kyori:adventure-api:4.8.1' 5 | implementation 'com.google.guava:guava:19.0' 6 | } -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/command/Command.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.command; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStats; 4 | 5 | public abstract class Command { 6 | public ServerStats serverStats; 7 | public String name; 8 | public String desc; 9 | public boolean op; 10 | 11 | public Command(ServerStats serverStats, String name, String desc, boolean op) { 12 | this.serverStats = serverStats; 13 | this.name = name; 14 | this.desc = desc; 15 | this.op = op; 16 | } 17 | 18 | public abstract void execute(Context sender); 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/command/CommandExecutor.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.command; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStats; 4 | 5 | import java.util.List; 6 | 7 | import static net.kyori.adventure.text.Component.*; 8 | import static net.kyori.adventure.text.format.NamedTextColor.RED; 9 | 10 | public interface CommandExecutor{ 11 | ServerStats getPlatform(); 12 | 13 | default void executeCommand(List args, Context ctx) { 14 | ServerStats serverStats = this.getPlatform(); 15 | 16 | if (args.size() == 0) { 17 | serverStats.getCommand("help").execute(ctx); 18 | } else { 19 | String cmd = args.get(0).toLowerCase(); 20 | Command command = serverStats.getCommand(cmd); 21 | 22 | if (command.op && !ctx.isOp()) { 23 | ctx.sendMessage( 24 | join( 25 | newline(), 26 | text("Not an operator...", RED) 27 | ) 28 | ); 29 | } 30 | 31 | command.execute(ctx); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/command/CompletionsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.command; 2 | 3 | public abstract class CompletionsManager { 4 | public final S suggester; 5 | private final Context ctx; 6 | 7 | public CompletionsManager(S s, Context ctx) { 8 | this.suggester = s; 9 | this.ctx = ctx; 10 | } 11 | 12 | public void register() { 13 | this.suggest("get"); 14 | this.suggest("help"); 15 | 16 | if (this.ctx.isOp()) { 17 | this.suggest("push"); 18 | this.suggest("toggle"); 19 | } 20 | } 21 | 22 | public void registerProxy() { 23 | this.suggest("get"); 24 | this.suggest("push"); 25 | this.suggest("toggle"); 26 | this.suggest("help"); 27 | } 28 | 29 | public abstract void suggest(String cmd); 30 | } 31 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/command/Context.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.command; 2 | 3 | import net.kyori.adventure.text.Component; 4 | 5 | public interface Context { 6 | void sendMessage(Component messages); 7 | boolean isOp(); 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/commands/GetCommand.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.commands; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import me.diced.serverstats.common.plugin.ServerStats; 6 | import me.diced.serverstats.common.command.Command; 7 | import me.diced.serverstats.common.command.Context; 8 | import net.kyori.adventure.text.Component; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static me.diced.serverstats.common.plugin.Util.formatBytes; 14 | import static net.kyori.adventure.text.Component.*; 15 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 16 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 17 | import static net.kyori.adventure.text.format.Style.style; 18 | import static net.kyori.adventure.text.format.TextDecoration.BOLD; 19 | 20 | public class GetCommand extends Command { 21 | public GetCommand(ServerStats serverStats) { 22 | super(serverStats, "get", "View current stats", false); 23 | } 24 | 25 | @Override 26 | public void execute(Context ctx) { 27 | Runtime rt = Runtime.getRuntime(); 28 | List components = new ArrayList<>(); 29 | 30 | for (Metric metric : MetricsManager.registeredMetrics) { 31 | if (!metric.isExemplar()) components.add(metric.formatComponent()); 32 | } 33 | 34 | ctx.sendMessage( 35 | join( 36 | newline(), 37 | text().append(text("Stats: ", style(GOLD, BOLD))), 38 | join( 39 | newline(), 40 | components 41 | ), 42 | text().append(text("Used Memory: ", GOLD)).append(text(formatBytes(rt.totalMemory() - rt.freeMemory()), WHITE)) 43 | ) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/commands/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.commands; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStats; 4 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 5 | import me.diced.serverstats.common.command.Command; 6 | import me.diced.serverstats.common.command.Context; 7 | import net.kyori.adventure.text.Component; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static net.kyori.adventure.text.Component.*; 14 | import static net.kyori.adventure.text.format.NamedTextColor.*; 15 | import static net.kyori.adventure.text.format.TextDecoration.*; 16 | import static net.kyori.adventure.text.format.Style.style; 17 | 18 | public class HelpCommand extends Command { 19 | public HelpCommand(ServerStats serverStats) { 20 | super(serverStats, "help", "See info about the plugin & see all commands", false); 21 | } 22 | 23 | @Override 24 | public void execute(Context ctx) { 25 | ServerStatsMetadata meta = this.serverStats.platform.getMetadata(); 26 | 27 | List components = new ArrayList<>(); 28 | 29 | for (Map.Entry cmd : this.serverStats.commands.entrySet()) { 30 | components.add(text("/stats " + cmd.getValue().name + " - " + cmd.getValue().desc)); 31 | } 32 | 33 | 34 | ctx.sendMessage( 35 | join( 36 | newline(), 37 | text("ServerStats " + meta.getType().toString() + " by " + meta.getAuthor(), style(WHITE, BOLD)), 38 | text("ServerStats Version: " + meta.getVersion(), GRAY), 39 | join( 40 | newline(), 41 | components 42 | ) 43 | ) 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/commands/PushCommand.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.commands; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStats; 4 | import me.diced.serverstats.common.command.Command; 5 | import me.diced.serverstats.common.command.Context; 6 | 7 | import static net.kyori.adventure.text.Component.*; 8 | import static net.kyori.adventure.text.format.NamedTextColor.*; 9 | 10 | public class PushCommand extends Command { 11 | public PushCommand(ServerStats serverStats) { 12 | super(serverStats, "push", "Push current stats", true); 13 | } 14 | 15 | @Override 16 | public void execute(Context ctx) { 17 | ctx.sendMessage( 18 | join( 19 | newline(), 20 | text("Pushed stats", BLUE) 21 | ) 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/commands/ToggleCommand.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.commands; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStats; 4 | import me.diced.serverstats.common.command.Command; 5 | import me.diced.serverstats.common.command.Context; 6 | 7 | import static net.kyori.adventure.text.Component.*; 8 | import static net.kyori.adventure.text.format.NamedTextColor.*; 9 | import static net.kyori.adventure.text.format.Style.style; 10 | 11 | public class ToggleCommand extends Command { 12 | public ToggleCommand(ServerStats serverStats) { 13 | super(serverStats, "toggle", "Toggle the interval from running", true); 14 | } 15 | 16 | @Override 17 | public void execute(Context ctx) { 18 | boolean toggled = this.serverStats.toggleInterval(); 19 | 20 | if (toggled) { 21 | ctx.sendMessage( 22 | join( 23 | newline(), 24 | text("Interval is now running", GREEN) 25 | ) 26 | ); 27 | } else { 28 | ctx.sendMessage( 29 | join( 30 | newline(), 31 | text("Interval is no longer running", RED) 32 | ) 33 | ); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/config/ConfigLoader.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.config; 2 | 3 | import java.nio.file.Path; 4 | 5 | import org.checkerframework.checker.nullness.qual.Nullable; 6 | import org.spongepowered.configurate.CommentedConfigurationNode; 7 | import org.spongepowered.configurate.ConfigurateException; 8 | import org.spongepowered.configurate.hocon.HoconConfigurationLoader; 9 | 10 | public class ConfigLoader { 11 | private final HoconConfigurationLoader loader; 12 | private final Class clazz; 13 | private final Path path; 14 | 15 | public ConfigLoader(Class clazz, Path path) { 16 | this.clazz = clazz; 17 | this.path = path; 18 | this.loader = HoconConfigurationLoader.builder() 19 | .path(path) 20 | .defaultOptions(opts -> opts.shouldCopyDefaults(true)) 21 | .build(); 22 | } 23 | 24 | public T load() throws ConfigurateException { 25 | final CommentedConfigurationNode node = this.loader.load(); 26 | final @Nullable T config = node.get(this.clazz); 27 | 28 | if (!this.path.toFile().exists()) { 29 | this.loader.save(node); 30 | } 31 | 32 | return config; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/config/ServerStatsConfig.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.config; 2 | 3 | 4 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 5 | 6 | @ConfigSerializable 7 | public class ServerStatsConfig { 8 | public ServerStatsWebServerConfig webServer; 9 | public ServerStatsPushableConfig pushable; 10 | 11 | public ServerStatsLogsConfig logs; 12 | 13 | public int interval = 15000; 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/config/ServerStatsLogsConfig.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.config; 2 | 3 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 4 | 5 | @ConfigSerializable 6 | public class ServerStatsLogsConfig { 7 | public boolean writeLogs = false; 8 | public String writeLog = "Wrote stats"; 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/config/ServerStatsPushableConfig.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.config; 2 | 3 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 4 | 5 | @ConfigSerializable 6 | public class ServerStatsPushableConfig { 7 | public boolean playerCount = true; 8 | public boolean freeMemory = true; 9 | public boolean maxMemory = true; 10 | public boolean totalMemory = true; 11 | public boolean tps = true; 12 | public boolean mspt = true; 13 | public boolean cpu = true; 14 | public boolean loadedChunks = true; 15 | public boolean entityCount = true; 16 | public boolean diskSpace = true; 17 | public boolean packets = true; 18 | public boolean gc = true; 19 | public boolean threads = true; 20 | public boolean uptime = true; 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/config/ServerStatsWebServerConfig.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.config; 2 | 3 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 4 | 5 | @ConfigSerializable 6 | public class ServerStatsWebServerConfig { 7 | public int port = 8000; 8 | public String hostname = "0.0.0.0"; 9 | public String route = "/metrics"; 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/plugin/LogWrapper.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.plugin; 2 | 3 | public interface LogWrapper { 4 | void info(String msg); 5 | void error(String msg); 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/plugin/ServerStats.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.plugin; 2 | 3 | import me.diced.serverstats.common.command.Command; 4 | import me.diced.serverstats.common.commands.GetCommand; 5 | import me.diced.serverstats.common.commands.HelpCommand; 6 | import me.diced.serverstats.common.commands.ToggleCommand; 7 | import me.diced.serverstats.common.commands.PushCommand; 8 | import me.diced.serverstats.common.config.ConfigLoader; 9 | import me.diced.serverstats.common.config.ServerStatsConfig; 10 | import me.diced.serverstats.common.prometheus.MetricsHttpServer; 11 | import me.diced.serverstats.common.scheduler.Scheduler; 12 | import me.diced.serverstats.common.scheduler.Task; 13 | 14 | import java.io.IOException; 15 | import java.net.InetSocketAddress; 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | public class ServerStats { 23 | public MetricsHttpServer webServer; 24 | public ServerStatsConfig config; 25 | public ServerStatsTasks tasks; 26 | public ServerStatsPlatform platform; 27 | public LogWrapper logger; 28 | public Map commands = new HashMap<>(); 29 | 30 | private boolean intervalRunning = true; 31 | 32 | public ServerStats(ServerStatsPlatform platform, LogWrapper logger) throws IOException { 33 | this.platform = platform; 34 | this.logger = logger; 35 | 36 | ConfigLoader configLoader = new ConfigLoader<>(ServerStatsConfig.class, this.platform.getConfigPath()); 37 | 38 | this.config = configLoader.load(); 39 | 40 | this.webServer = new MetricsHttpServer(new InetSocketAddress(this.config.webServer.hostname, this.config.webServer.port), this.config.webServer.route); 41 | 42 | this.tasks = new ServerStatsTasks(this, platform.getScheduler()); 43 | 44 | this.commands.put("help", new HelpCommand(this)); 45 | this.commands.put("get", new GetCommand(this)); 46 | this.commands.put("push", new PushCommand(this)); 47 | this.commands.put("toggle", new ToggleCommand(this)); 48 | } 49 | 50 | public void pushStats() { 51 | if (this.intervalRunning) { 52 | this.platform.getMetricsManager().push(); 53 | 54 | if (this.config.logs.writeLogs) { 55 | this.logger.info(this.config.logs.writeLog); 56 | } 57 | } 58 | } 59 | 60 | public void stop() { 61 | this.logger.info("Stopping ServerStats-Worker"); 62 | this.tasks.stop(); 63 | } 64 | 65 | public boolean toggleInterval() { 66 | this.intervalRunning = !this.intervalRunning; 67 | return this.intervalRunning; 68 | } 69 | 70 | public Command getCommand(String name) { 71 | Command cmd = this.commands.get(name); 72 | if (cmd == null) cmd = this.commands.get("help"); 73 | 74 | return cmd; 75 | } 76 | 77 | public static class ServerStatsTasks { 78 | private final List tasks = new ArrayList<>(); 79 | private final ServerStats serverStats; 80 | private final Scheduler scheduler; 81 | 82 | public ServerStatsTasks(ServerStats serverStats, Scheduler scheduler) { 83 | this.serverStats = serverStats; 84 | this.scheduler = scheduler; 85 | } 86 | 87 | public void register() { 88 | this.serverStats.logger.info("Starting ServerStats-Worker tasks"); 89 | 90 | var webTask = this.scheduler.schedule(() -> { 91 | this.serverStats.webServer.start(); 92 | 93 | String address = this.serverStats.webServer.addr.getHostName() + ":" + this.serverStats.webServer.addr.getPort(); 94 | this.serverStats.logger.info("Started Prometheus Exporter on " + address); 95 | }); 96 | 97 | this.serverStats.pushStats(); 98 | var statsTask = this.scheduler.scheduleRepeatingTask(() -> this.serverStats.pushStats(), this.serverStats.config.interval, TimeUnit.MILLISECONDS); 99 | 100 | this.tasks.add(webTask); 101 | this.tasks.add(statsTask); 102 | } 103 | 104 | public void stop() { 105 | this.tasks.forEach(t -> t.cancel()); 106 | this.scheduler.stop(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/plugin/ServerStatsMetadata.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.plugin; 2 | 3 | public interface ServerStatsMetadata { 4 | ServerStatsType getType(); 5 | String getVersion(); 6 | String getAuthor(); 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/plugin/ServerStatsPlatform.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.plugin; 2 | 3 | import me.diced.serverstats.common.prometheus.MetricsManager; 4 | import me.diced.serverstats.common.scheduler.Scheduler; 5 | 6 | import java.nio.file.Path; 7 | 8 | public interface ServerStatsPlatform { 9 | Path getConfigPath(); 10 | Scheduler getScheduler(); 11 | ServerStatsMetadata getMetadata(); 12 | MetricsManager getMetricsManager(); 13 | 14 | ServerStats getServerStats(); 15 | void start(); 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/plugin/ServerStatsType.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.plugin; 2 | 3 | public enum ServerStatsType { 4 | BUKKIT, 5 | FABRIC, 6 | BUNGEE, 7 | VELOCITY; 8 | 9 | public String toString() { 10 | if (this == FABRIC) return "Fabric"; 11 | else if (this == BUKKIT) return "Fabric"; 12 | else if (this == BUNGEE) return "Bungee"; 13 | else if (this == VELOCITY) return "Velocity"; 14 | else return "Bukkit"; 15 | } 16 | 17 | public boolean isProxy() { 18 | if (this == BUNGEE) return true; 19 | else if (this == VELOCITY) return true; 20 | else return false; 21 | } 22 | } -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/plugin/Util.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.plugin; 2 | 3 | import com.sun.management.OperatingSystemMXBean; 4 | import net.kyori.adventure.text.format.NamedTextColor; 5 | 6 | import java.io.IOException; 7 | import java.lang.management.ManagementFactory; 8 | import java.math.BigDecimal; 9 | import java.math.RoundingMode; 10 | import java.nio.file.*; 11 | import java.nio.file.attribute.BasicFileAttributes; 12 | import java.time.Duration; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | import static java.nio.file.FileVisitResult.*; 17 | 18 | public class Util { 19 | public static String[] units = new String[] {"B", "kB", "MB", "GB", "TB", "PB" }; 20 | 21 | public static List tokenize(String str) { 22 | return Arrays.asList(str.split(" ")); 23 | } 24 | public static List tokenize(String[] strings) { 25 | return Arrays.asList(strings); 26 | } 27 | 28 | public static double cpuPercent() { 29 | return ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getProcessCpuLoad() * 100; 30 | } 31 | 32 | public static NamedTextColor heatmapColor(double actual, double reference) { // from carpet mod Messenger class 33 | NamedTextColor color = NamedTextColor.GRAY; 34 | 35 | if (actual >= 0.0D) color = NamedTextColor.DARK_GREEN; 36 | if (actual > 0.5D * reference) color = NamedTextColor.YELLOW; 37 | if (actual > 0.8D * reference) color = NamedTextColor.RED; 38 | if (actual > reference) color = NamedTextColor.LIGHT_PURPLE; 39 | 40 | return color; 41 | } 42 | 43 | public static long getDirectorySize(Path path) throws IOException { 44 | DirectoryReader reader = new DirectoryReader(path); 45 | Files.walkFileTree(path, reader); 46 | 47 | return reader.size; 48 | } 49 | 50 | public static String formatBytes(long bytes) { 51 | int num = 0; 52 | 53 | while (bytes > 1024) { 54 | bytes /= 1024; 55 | ++num; 56 | } 57 | 58 | String d = new BigDecimal(bytes).setScale(0, RoundingMode.HALF_UP).toString(); 59 | 60 | return d + " " + units[num]; 61 | } 62 | 63 | public static String formatDuration(long duration) { 64 | Duration d = Duration.ofMillis(duration); 65 | 66 | long days = d.toDays(); 67 | d = d.minusDays(days); 68 | 69 | long hours = d.toHours(); 70 | d = d.minusHours(hours); 71 | 72 | long minutes = d.toMinutes(); 73 | d = d.minusMinutes(minutes); 74 | 75 | long seconds = d.getSeconds(); 76 | 77 | String str = ""; 78 | 79 | if (days > 0) str += days + "d "; 80 | if (hours > 0) str += hours + "h "; 81 | if (minutes > 0) str += minutes + "m "; 82 | if (seconds > 0) str += seconds + "s "; 83 | 84 | return str; 85 | } 86 | 87 | private static class DirectoryReader extends SimpleFileVisitor { 88 | public long size = 0; 89 | private final Path root; 90 | 91 | public DirectoryReader(Path root) { 92 | this.root = root; 93 | } 94 | 95 | @Override 96 | public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attr) { 97 | if (!path.equals(this.root) && path.getFileName().toString().startsWith("DIM")) return SKIP_SUBTREE; 98 | return CONTINUE; 99 | } 100 | 101 | @Override 102 | public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { 103 | size += attr.size(); 104 | return CONTINUE; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/Metric.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus; 2 | 3 | import net.kyori.adventure.text.Component; 4 | 5 | public abstract class Metric { 6 | public String name; 7 | public String type; 8 | public final MetricsManager manager; 9 | public T collector; 10 | 11 | public Metric(String name, String type, MetricsManager manager, T collector) { 12 | this.name = name; 13 | this.type = type; 14 | this.manager = manager; 15 | this.collector = collector; 16 | 17 | MetricsManager.registeredMetrics.add(this); 18 | } 19 | 20 | public abstract void run(); 21 | 22 | public String formatPrometheus() { 23 | return ""; 24 | } 25 | public Component formatComponent() { 26 | return Component.text(""); // default for exemplar stuff, but overwritten for non exemplar 27 | } 28 | public boolean enabled() { 29 | return true; 30 | } 31 | public boolean isExemplar() { 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/MetricsExporter.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus; 2 | 3 | import java.io.StringWriter; 4 | 5 | public class MetricsExporter { 6 | public static String export() { 7 | StringWriter writer = new StringWriter(); 8 | 9 | for (Metric metric : MetricsManager.registeredMetrics) { 10 | if (metric.enabled()) { 11 | writer.write("# HELP " + metric.name + '\n'); 12 | writer.write("# TYPE " + metric.name + " " + metric.type + '\n'); 13 | writer.write(metric.formatPrometheus() + '\n'); 14 | } 15 | } 16 | 17 | return writer.toString(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/MetricsHttpServer.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus; 2 | 3 | import com.sun.net.httpserver.HttpExchange; 4 | import com.sun.net.httpserver.HttpHandler; 5 | import com.sun.net.httpserver.HttpServer; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.net.InetSocketAddress; 10 | 11 | public class MetricsHttpServer { 12 | private final HttpServer server; 13 | public final InetSocketAddress addr; 14 | 15 | public MetricsHttpServer(InetSocketAddress addr, String route) throws IOException { 16 | this.server = HttpServer.create(addr, 0); 17 | this.addr = addr; 18 | 19 | this.server.createContext(route, new StatsMetricsHandler()); 20 | this.server.setExecutor(null); 21 | } 22 | 23 | public void start() { 24 | this.server.start(); 25 | } 26 | 27 | private static class StatsMetricsHandler implements HttpHandler { 28 | @Override 29 | public void handle(HttpExchange exchange) throws IOException { 30 | String metrics = MetricsExporter.export(); 31 | 32 | OutputStream os = exchange.getResponseBody(); 33 | exchange.sendResponseHeaders(200, metrics.length()); 34 | 35 | os.write(metrics.getBytes()); 36 | os.flush(); 37 | os.close(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/MetricsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus; 2 | 3 | import me.diced.serverstats.common.config.ServerStatsConfig; 4 | import me.diced.serverstats.common.plugin.ServerStats; 5 | import me.diced.serverstats.common.prometheus.metrics.jvm.*; 6 | import me.diced.serverstats.common.prometheus.metrics.server.*; 7 | import me.diced.serverstats.common.prometheus.metrics.world.DiskSpacePerWorld; 8 | import me.diced.serverstats.common.prometheus.metrics.world.EntityCountPerWorld; 9 | import me.diced.serverstats.common.prometheus.metrics.world.LoadedChunksPerWorld; 10 | 11 | import java.nio.file.Path; 12 | import java.util.TreeMap; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.List; 16 | import java.util.ArrayList; 17 | 18 | public abstract class MetricsManager { 19 | 20 | public static List> registeredMetrics = new ArrayList<>(); 21 | 22 | private final Map> metrics = new HashMap<>(); 23 | public final ServerStatsConfig config; 24 | 25 | public MetricsManager(ServerStats serverStats) { 26 | this.config = serverStats.config; 27 | 28 | this.metrics.put("player_count", new PlayerCount("player_count", this)); 29 | this.metrics.put("cpu", new CPU("cpu", this)); 30 | this.metrics.put("uptime", new Uptime("uptime", this)); 31 | this.metrics.put("threads", new Threads("threads", this)); 32 | this.metrics.put("gc", new GC("gc", this)); 33 | 34 | if (!serverStats.platform.getMetadata().getType().isProxy()) { // only add game server stuff if not a proxy server 35 | this.metrics.put("tps", new TPS("tps", this)); 36 | this.metrics.put("mspt", new MSPT("mspt", this)); 37 | this.metrics.put("loaded_chunks", new LoadedChunks("loaded_chunks", this)); 38 | this.metrics.put("entity_count", new EntityCount("entity_count", this)); 39 | 40 | this.metrics.put("packets_rx", new PacketRX("packets_rx", this)); 41 | this.metrics.put("packets_tx", new PacketTX("packets_tx", this)); 42 | 43 | this.metrics.put("disk_space_world", new DiskSpacePerWorld("disk_space_world", this)); 44 | this.metrics.put("loaded_chunks_world", new LoadedChunksPerWorld("loaded_chunks_world", this)); 45 | this.metrics.put("entity_count_world", new EntityCountPerWorld("entity_count_world", this)); 46 | } 47 | 48 | 49 | this.metrics.put("free_memory", new FreeMemory("free_memory", this)); 50 | this.metrics.put("total_memory", new TotalMemory("total_memory", this)); 51 | this.metrics.put("max_memory", new MaxMemory("max_memory", this)); 52 | } 53 | 54 | public void push() { 55 | this.metrics.values().forEach(m -> { 56 | if (m.enabled()) m.run(); 57 | }); 58 | } 59 | 60 | public int getPlayerCount() { 61 | return 0; 62 | } 63 | 64 | public double getMspt() { 65 | return 0; 66 | } 67 | 68 | public double getTps(){ 69 | return 0; 70 | } 71 | 72 | public int getLoadedChunks() { 73 | return 0; 74 | } 75 | 76 | public int getEntityCount() { 77 | return 0; 78 | } 79 | 80 | public long getPacketRx() { return 0; } 81 | 82 | public long getPacketTx() { return 0; } 83 | 84 | public TreeMap getLoadedChunksPerWorld() { 85 | return new TreeMap<>(); 86 | } 87 | 88 | public TreeMap getEntityCountPerWorld() { 89 | return new TreeMap<>(); 90 | } 91 | 92 | public TreeMap getWorldPaths() { 93 | return new TreeMap<>(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/jvm/CPU.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.jvm; 2 | 3 | import com.google.common.util.concurrent.AtomicDouble; 4 | import me.diced.serverstats.common.plugin.Util; 5 | import me.diced.serverstats.common.prometheus.Metric; 6 | import me.diced.serverstats.common.prometheus.MetricsManager; 7 | import net.kyori.adventure.text.Component; 8 | 9 | import java.math.BigDecimal; 10 | import java.math.RoundingMode; 11 | 12 | import static net.kyori.adventure.text.Component.text; 13 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 14 | import static me.diced.serverstats.common.plugin.Util.heatmapColor; 15 | 16 | public class CPU extends Metric { 17 | 18 | public CPU(String name, MetricsManager manager) { 19 | super(name, "gauge", manager, new AtomicDouble()); 20 | } 21 | 22 | @Override 23 | public void run() { 24 | this.collector.set(Util.cpuPercent()); 25 | } 26 | 27 | @Override 28 | public String formatPrometheus() { 29 | return String.format("%s %f", this.name, this.collector.doubleValue()); 30 | } 31 | 32 | @Override 33 | public Component formatComponent() { 34 | String d = BigDecimal.valueOf(Util.cpuPercent()).setScale(2, RoundingMode.HALF_UP).toString(); 35 | return text().append(text("CPU: ", GOLD)).append(text(d, heatmapColor(this.collector.doubleValue(), 100.0f))).asComponent(); 36 | } 37 | 38 | @Override 39 | public boolean enabled() { 40 | return this.manager.config.pushable.cpu; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/jvm/FreeMemory.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.jvm; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | import static me.diced.serverstats.common.plugin.Util.formatBytes; 10 | import static net.kyori.adventure.text.Component.text; 11 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 12 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 13 | 14 | public class FreeMemory extends Metric { 15 | 16 | public FreeMemory(String name, MetricsManager manager) { 17 | super(name, "gauge", manager, new AtomicLong()); 18 | } 19 | 20 | @Override 21 | public void run() { 22 | Runtime runtime = Runtime.getRuntime(); 23 | 24 | this.collector.set(runtime.freeMemory()); 25 | } 26 | 27 | @Override 28 | public String formatPrometheus() { 29 | return String.format("%s %d", this.name, this.collector.longValue()); 30 | } 31 | 32 | @Override 33 | public Component formatComponent() { 34 | return text().append(text("Free Memory: ", GOLD)).append(text(formatBytes(this.collector.longValue()), WHITE)).asComponent(); 35 | } 36 | 37 | @Override 38 | public boolean enabled() { 39 | return this.manager.config.pushable.freeMemory; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/jvm/GC.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.jvm; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | 6 | import java.io.StringWriter; 7 | import java.lang.management.GarbageCollectorMXBean; 8 | import java.lang.management.ManagementFactory; 9 | import java.util.Map; 10 | import java.util.TreeMap; 11 | 12 | public class GC extends Metric> { 13 | 14 | public GC(String name, MetricsManager manager) { 15 | super(name, "summary", manager, new TreeMap<>()); 16 | } 17 | 18 | @Override 19 | public void run() { 20 | for (final GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) { 21 | this.collector.put(gc.getName(), gc.getCollectionTime()); 22 | } 23 | } 24 | 25 | @Override 26 | public String formatPrometheus() { 27 | StringWriter writer = new StringWriter(); 28 | 29 | var last = this.collector.lastEntry(); 30 | 31 | for (Map.Entry entry : this.collector.entrySet()) { 32 | writer.write(this.name + "{name=\"" + entry.getKey() + "\"} " + entry.getValue() + (last.getKey().equals(entry.getKey()) ? "" : '\n')); 33 | } 34 | 35 | return writer.toString(); 36 | } 37 | 38 | @Override 39 | public boolean isExemplar() { 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean enabled() { 45 | return this.manager.config.pushable.gc; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/jvm/MaxMemory.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.jvm; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | import static me.diced.serverstats.common.plugin.Util.formatBytes; 10 | import static net.kyori.adventure.text.Component.text; 11 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 12 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 13 | 14 | public class MaxMemory extends Metric { 15 | 16 | public MaxMemory(String name, MetricsManager manager) { 17 | super(name, "gauge", manager, new AtomicLong()); 18 | } 19 | 20 | @Override 21 | public void run() { 22 | Runtime runtime = Runtime.getRuntime(); 23 | 24 | this.collector.set(runtime.maxMemory()); 25 | } 26 | 27 | @Override 28 | public String formatPrometheus() { 29 | return String.format("%s %d", this.name, this.collector.longValue()); 30 | } 31 | 32 | @Override 33 | public Component formatComponent() { 34 | return text().append(text("Max Memory: ", GOLD)).append(text(formatBytes(this.collector.longValue()), WHITE)).asComponent(); 35 | } 36 | 37 | @Override 38 | public boolean enabled() { 39 | return this.manager.config.pushable.maxMemory; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/jvm/Threads.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.jvm; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.lang.management.ManagementFactory; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | import static net.kyori.adventure.text.Component.text; 11 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 12 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 13 | 14 | public class Threads extends Metric { 15 | 16 | public Threads(String name, MetricsManager manager) { 17 | super(name, "gauge", manager, new AtomicInteger()); 18 | } 19 | 20 | @Override 21 | public void run() { 22 | this.collector.set(ManagementFactory.getThreadMXBean().getThreadCount()); 23 | } 24 | 25 | @Override 26 | public String formatPrometheus() { 27 | return String.format("%s %f", this.name, this.collector.doubleValue()); 28 | } 29 | 30 | @Override 31 | public Component formatComponent() { 32 | return text().append(text("Theads: ", GOLD)).append(text(this.collector.intValue(), WHITE)).asComponent(); 33 | } 34 | 35 | @Override 36 | public boolean enabled() { 37 | return this.manager.config.pushable.threads; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/jvm/TotalMemory.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.jvm; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | import static me.diced.serverstats.common.plugin.Util.formatBytes; 10 | import static net.kyori.adventure.text.Component.text; 11 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 12 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 13 | 14 | public class TotalMemory extends Metric { 15 | 16 | public TotalMemory(String name, MetricsManager manager) { 17 | super(name, "gauge", manager, new AtomicLong()); 18 | } 19 | 20 | @Override 21 | public void run() { 22 | Runtime runtime = Runtime.getRuntime(); 23 | 24 | this.collector.set(runtime.totalMemory()); 25 | } 26 | 27 | @Override 28 | public String formatPrometheus() { 29 | return String.format("%s %d", this.name, this.collector.longValue()); 30 | } 31 | 32 | @Override 33 | public Component formatComponent() { 34 | return text().append(text("Total Memory: ", GOLD)).append(text(formatBytes(this.collector.longValue()), WHITE)).asComponent(); 35 | } 36 | 37 | @Override 38 | public boolean enabled() { 39 | return this.manager.config.pushable.totalMemory; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/jvm/Uptime.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.jvm; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.lang.management.ManagementFactory; 8 | import java.util.concurrent.atomic.AtomicLong; 9 | 10 | import static me.diced.serverstats.common.plugin.Util.formatDuration; 11 | import static net.kyori.adventure.text.Component.text; 12 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 13 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 14 | 15 | public class Uptime extends Metric { 16 | 17 | public Uptime(String name, MetricsManager manager) { 18 | super(name, "gauge", manager, new AtomicLong()); 19 | } 20 | 21 | @Override 22 | public void run() { 23 | this.collector.set(ManagementFactory.getRuntimeMXBean().getUptime()); 24 | } 25 | 26 | @Override 27 | public String formatPrometheus() { 28 | return String.format("%s %d", this.name, this.collector.longValue()); 29 | } 30 | 31 | @Override 32 | public Component formatComponent() { 33 | return text().append(text("Uptime: ", GOLD)).append(text(formatDuration(this.collector.longValue()), WHITE)).asComponent(); 34 | } 35 | 36 | @Override 37 | public boolean enabled() { 38 | return this.manager.config.pushable.uptime; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/server/EntityCount.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.server; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | import static net.kyori.adventure.text.Component.text; 10 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 11 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 12 | 13 | public class EntityCount extends Metric { 14 | 15 | public EntityCount(String name, MetricsManager manager) { 16 | super(name, "gauge", manager, new AtomicInteger()); 17 | } 18 | 19 | @Override 20 | public void run() { 21 | this.collector.set(this.manager.getEntityCount()); 22 | } 23 | 24 | @Override 25 | public String formatPrometheus() { 26 | return String.format("%s %d", this.name, this.collector.intValue()); 27 | } 28 | 29 | @Override 30 | public Component formatComponent() { 31 | return text().append(text("Entities: ", GOLD)).append(text(String.format("%d", this.collector.intValue()), WHITE)).asComponent(); 32 | } 33 | 34 | @Override 35 | public boolean enabled() { 36 | return this.manager.config.pushable.entityCount; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/server/LoadedChunks.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.server; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | import static net.kyori.adventure.text.Component.text; 10 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 11 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 12 | 13 | public class LoadedChunks extends Metric { 14 | 15 | public LoadedChunks(String name, MetricsManager manager) { 16 | super(name, "gauge", manager, new AtomicInteger()); 17 | } 18 | 19 | @Override 20 | public void run() { 21 | this.collector.set(this.manager.getLoadedChunks()); 22 | } 23 | 24 | @Override 25 | public String formatPrometheus() { 26 | return String.format("%s %d", this.name, this.collector.intValue()); 27 | } 28 | 29 | @Override 30 | public Component formatComponent() { 31 | return text().append(text("Loaded Chunks: ", GOLD)).append(text(String.format("%d", this.collector.intValue()), WHITE)).asComponent(); 32 | } 33 | 34 | @Override 35 | public boolean enabled() { 36 | return this.manager.config.pushable.loadedChunks; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/server/MSPT.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.server; 2 | 3 | import com.google.common.util.concurrent.AtomicDouble; 4 | import me.diced.serverstats.common.prometheus.Metric; 5 | import me.diced.serverstats.common.prometheus.MetricsManager; 6 | import net.kyori.adventure.text.Component; 7 | 8 | import static me.diced.serverstats.common.plugin.Util.heatmapColor; 9 | import static net.kyori.adventure.text.Component.text; 10 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 11 | 12 | public class MSPT extends Metric { 13 | 14 | public MSPT(String name, MetricsManager manager) { 15 | super(name, "gauge", manager, new AtomicDouble()); 16 | } 17 | 18 | @Override 19 | public void run() { 20 | this.collector.set(this.manager.getMspt()); 21 | } 22 | 23 | @Override 24 | public String formatPrometheus() { 25 | return String.format("%s %f", this.name, this.collector.doubleValue()); 26 | } 27 | 28 | @Override 29 | public Component formatComponent() { 30 | return text().append(text("MSPT: ", GOLD)).append(text(String.format("%.1f ms", this.collector.doubleValue()), heatmapColor(this.collector.doubleValue(), 50))).asComponent(); 31 | } 32 | 33 | @Override 34 | public boolean enabled() { 35 | return this.manager.config.pushable.mspt; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/server/PacketRX.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.server; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | import static net.kyori.adventure.text.Component.text; 10 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 11 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 12 | 13 | public class PacketRX extends Metric { 14 | 15 | public PacketRX(String name, MetricsManager manager) { 16 | super(name, "gauge", manager, new AtomicLong()); 17 | } 18 | 19 | @Override 20 | public void run() { 21 | this.collector.set(this.manager.getPacketRx()); 22 | } 23 | 24 | @Override 25 | public String formatPrometheus() { 26 | return String.format("%s %d", this.name, this.collector.longValue()); 27 | } 28 | 29 | @Override 30 | public Component formatComponent() { 31 | return text().append(text("Packets Read: ", GOLD)).append(text(String.format("%d", this.collector.longValue()), WHITE)).asComponent(); 32 | } 33 | 34 | @Override 35 | public boolean enabled() { 36 | return this.manager.config.pushable.packets; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/server/PacketTX.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.server; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | import static net.kyori.adventure.text.Component.text; 10 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 11 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 12 | 13 | public class PacketTX extends Metric { 14 | 15 | public PacketTX(String name, MetricsManager manager) { 16 | super(name, "gauge", manager, new AtomicLong()); 17 | } 18 | 19 | @Override 20 | public void run() { 21 | this.collector.set(this.manager.getPacketTx()); 22 | } 23 | 24 | @Override 25 | public String formatPrometheus() { 26 | return String.format("%s %d", this.name, this.collector.longValue()); 27 | } 28 | 29 | @Override 30 | public Component formatComponent() { 31 | return text().append(text("Packets Sent: ", GOLD)).append(text(String.format("%d", this.collector.longValue()), WHITE)).asComponent(); 32 | } 33 | 34 | @Override 35 | public boolean enabled() { 36 | return this.manager.config.pushable.packets; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/server/PlayerCount.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.server; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | import static net.kyori.adventure.text.Component.text; 10 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 11 | import static net.kyori.adventure.text.format.NamedTextColor.WHITE; 12 | 13 | public class PlayerCount extends Metric { 14 | 15 | public PlayerCount(String name, MetricsManager manager) { 16 | super(name, "gauge", manager, new AtomicInteger()); 17 | } 18 | 19 | @Override 20 | public void run() { 21 | this.collector.set(this.manager.getPlayerCount()); 22 | } 23 | 24 | @Override 25 | public String formatPrometheus() { 26 | return String.format("%s %d", this.name, this.collector.intValue()); 27 | } 28 | 29 | @Override 30 | public Component formatComponent() { 31 | return text().append(text("Players: ", GOLD)).append(text(String.format("%d", this.collector.intValue()), WHITE)).asComponent(); 32 | } 33 | 34 | @Override 35 | public boolean enabled() { 36 | return this.manager.config.pushable.playerCount; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/server/TPS.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.server; 2 | 3 | import com.google.common.util.concurrent.AtomicDouble; 4 | import me.diced.serverstats.common.prometheus.Metric; 5 | import me.diced.serverstats.common.prometheus.MetricsManager; 6 | import net.kyori.adventure.text.Component; 7 | 8 | import static me.diced.serverstats.common.plugin.Util.heatmapColor; 9 | import static net.kyori.adventure.text.Component.text; 10 | import static net.kyori.adventure.text.format.NamedTextColor.GOLD; 11 | 12 | public class TPS extends Metric { 13 | 14 | public TPS(String name, MetricsManager manager) { 15 | super(name, "gauge", manager, new AtomicDouble()); 16 | } 17 | 18 | @Override 19 | public void run() { 20 | this.collector.set(this.manager.getTps()); 21 | } 22 | 23 | @Override 24 | public String formatPrometheus() { 25 | return String.format("%s %f", this.name, this.collector.doubleValue()); 26 | } 27 | 28 | @Override 29 | public Component formatComponent() { 30 | return text().append(text("TPS: ", GOLD)).append(text(String.format("%.1f", this.collector.doubleValue()), heatmapColor(this.manager.getMspt(), 50))).asComponent(); 31 | } 32 | 33 | @Override 34 | public boolean enabled() { 35 | return this.manager.config.pushable.tps; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/world/DiskSpacePerWorld.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.world; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | import net.kyori.adventure.text.Component; 6 | 7 | import java.io.IOException; 8 | import java.io.StringWriter; 9 | import java.nio.file.Path; 10 | import java.util.Map; 11 | import java.util.TreeMap; 12 | 13 | import static me.diced.serverstats.common.plugin.Util.getDirectorySize; 14 | 15 | public class DiskSpacePerWorld extends Metric> { 16 | 17 | public DiskSpacePerWorld(String name, MetricsManager manager) { 18 | super(name, "counter", manager, new TreeMap<>()); 19 | } 20 | 21 | @Override 22 | public void run() { 23 | for (Map.Entry entry : this.manager.getWorldPaths().entrySet()) { 24 | try { 25 | this.collector.put(entry.getKey(), getDirectorySize(entry.getValue())); 26 | } catch (IOException e) { 27 | this.collector.put(entry.getKey(), 0L); 28 | } 29 | } 30 | } 31 | 32 | @Override 33 | public String formatPrometheus() { 34 | StringWriter writer = new StringWriter(); 35 | 36 | var last = this.collector.lastEntry(); 37 | 38 | for (Map.Entry entry : this.collector.entrySet()) { 39 | writer.write(this.name + "{world=\"" + entry.getKey() + "\"} " + entry.getValue() + (last.getKey().equals(entry.getKey()) ? "" : '\n')); 40 | } 41 | 42 | return writer.toString(); 43 | } 44 | 45 | @Override 46 | public boolean isExemplar() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean enabled() { 52 | return this.manager.config.pushable.diskSpace; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/world/EntityCountPerWorld.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.world; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | 6 | import java.io.StringWriter; 7 | import java.util.Map; 8 | import java.util.TreeMap; 9 | 10 | public class EntityCountPerWorld extends Metric> { 11 | 12 | public EntityCountPerWorld(String name, MetricsManager manager) { 13 | super(name, "counter", manager, new TreeMap<>()); 14 | } 15 | 16 | @Override 17 | public void run() { 18 | this.collector = this.manager.getEntityCountPerWorld(); 19 | } 20 | 21 | @Override 22 | public String formatPrometheus() { 23 | StringWriter writer = new StringWriter(); 24 | 25 | var last = this.collector.lastEntry(); 26 | 27 | for (Map.Entry entry : this.collector.entrySet()) { 28 | writer.write(this.name + "{world=\"" + entry.getKey() + "\"} " + entry.getValue() + (last.getKey().equals(entry.getKey()) ? "" : '\n')); 29 | } 30 | 31 | return writer.toString(); 32 | } 33 | 34 | @Override 35 | public boolean enabled() { 36 | return this.manager.config.pushable.entityCount; 37 | } 38 | 39 | @Override 40 | public boolean isExemplar() { 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/prometheus/metrics/world/LoadedChunksPerWorld.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.prometheus.metrics.world; 2 | 3 | import me.diced.serverstats.common.prometheus.Metric; 4 | import me.diced.serverstats.common.prometheus.MetricsManager; 5 | 6 | import java.io.StringWriter; 7 | import java.util.Map; 8 | import java.util.TreeMap; 9 | 10 | public class LoadedChunksPerWorld extends Metric> { 11 | 12 | public LoadedChunksPerWorld(String name, MetricsManager manager) { 13 | super(name, "counter", manager, new TreeMap<>()); 14 | } 15 | 16 | @Override 17 | public void run() { 18 | this.collector = this.manager.getLoadedChunksPerWorld(); 19 | } 20 | 21 | @Override 22 | public String formatPrometheus() { 23 | StringWriter writer = new StringWriter(); 24 | 25 | var last = this.collector.lastEntry(); 26 | 27 | for (Map.Entry entry : this.collector.entrySet()) { 28 | writer.write(this.name + "{world=\"" + entry.getKey() + "\"} " + entry.getValue() + (last.getKey().equals(entry.getKey()) ? "" : '\n')); 29 | } 30 | 31 | return writer.toString(); 32 | } 33 | 34 | @Override 35 | public boolean enabled() { 36 | return this.manager.config.pushable.loadedChunks; 37 | } 38 | 39 | @Override 40 | public boolean isExemplar() { 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/scheduler/Scheduler.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.scheduler; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public interface Scheduler { 6 | Task scheduleRepeatingTask(Runnable task, long interval, TimeUnit unit); 7 | Task schedule(Runnable task); 8 | void stop(); 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/scheduler/Task.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.scheduler; 2 | 3 | public interface Task { 4 | void cancel(); 5 | } 6 | -------------------------------------------------------------------------------- /common/src/main/java/me/diced/serverstats/common/scheduler/ThreadScheduler.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.common.scheduler; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | 5 | import java.util.concurrent.ScheduledFuture; 6 | import java.util.concurrent.ScheduledThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class ThreadScheduler implements Scheduler { 10 | private final ScheduledThreadPoolExecutor scheduler; 11 | 12 | public ThreadScheduler() { 13 | this.scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("ServerStats-Worker").build()); 14 | } 15 | 16 | public Task scheduleRepeatingTask(Runnable task, long interval, TimeUnit unit) { 17 | ScheduledFuture future = this.scheduler.scheduleAtFixedRate(task, interval, interval, unit); 18 | return () -> future.cancel(false); 19 | } 20 | 21 | public Task schedule(Runnable task) { 22 | ScheduledFuture future = this.scheduler.schedule(task, 0, TimeUnit.MILLISECONDS); 23 | return () -> future.cancel(false); 24 | } 25 | 26 | public void stop() { 27 | this.scheduler.shutdown(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example-grafana-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 1, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "datasource": null, 23 | "fieldConfig": { 24 | "defaults": { 25 | "color": { 26 | "mode": "palette-classic" 27 | }, 28 | "custom": { 29 | "axisLabel": "", 30 | "axisPlacement": "auto", 31 | "barAlignment": 0, 32 | "drawStyle": "line", 33 | "fillOpacity": 20, 34 | "gradientMode": "opacity", 35 | "hideFrom": { 36 | "legend": false, 37 | "tooltip": false, 38 | "viz": false 39 | }, 40 | "lineInterpolation": "linear", 41 | "lineWidth": 1, 42 | "pointSize": 5, 43 | "scaleDistribution": { 44 | "type": "linear" 45 | }, 46 | "showPoints": "auto", 47 | "spanNulls": false, 48 | "stacking": { 49 | "group": "A", 50 | "mode": "none" 51 | }, 52 | "thresholdsStyle": { 53 | "mode": "off" 54 | } 55 | }, 56 | "displayName": "CPU", 57 | "mappings": [], 58 | "max": 100, 59 | "min": 0, 60 | "thresholds": { 61 | "mode": "absolute", 62 | "steps": [ 63 | { 64 | "color": "green", 65 | "value": null 66 | } 67 | ] 68 | } 69 | }, 70 | "overrides": [] 71 | }, 72 | "gridPos": { 73 | "h": 7, 74 | "w": 19, 75 | "x": 0, 76 | "y": 0 77 | }, 78 | "id": 30, 79 | "options": { 80 | "legend": { 81 | "calcs": [], 82 | "displayMode": "hidden", 83 | "placement": "bottom" 84 | }, 85 | "tooltip": { 86 | "mode": "single" 87 | } 88 | }, 89 | "targets": [ 90 | { 91 | "exemplar": true, 92 | "expr": "cpu", 93 | "interval": "", 94 | "legendFormat": "", 95 | "refId": "A" 96 | } 97 | ], 98 | "title": "CPU", 99 | "type": "timeseries" 100 | }, 101 | { 102 | "datasource": null, 103 | "description": "", 104 | "fieldConfig": { 105 | "defaults": { 106 | "color": { 107 | "mode": "thresholds" 108 | }, 109 | "mappings": [], 110 | "max": 100, 111 | "min": 0, 112 | "thresholds": { 113 | "mode": "percentage", 114 | "steps": [ 115 | { 116 | "color": "green", 117 | "value": null 118 | }, 119 | { 120 | "color": "#EAB839", 121 | "value": 70 122 | }, 123 | { 124 | "color": "red", 125 | "value": 90 126 | } 127 | ] 128 | }, 129 | "unit": "%" 130 | }, 131 | "overrides": [] 132 | }, 133 | "gridPos": { 134 | "h": 7, 135 | "w": 5, 136 | "x": 19, 137 | "y": 0 138 | }, 139 | "id": 32, 140 | "options": { 141 | "colorMode": "value", 142 | "graphMode": "none", 143 | "justifyMode": "auto", 144 | "orientation": "auto", 145 | "reduceOptions": { 146 | "calcs": [ 147 | "lastNotNull" 148 | ], 149 | "fields": "", 150 | "values": false 151 | }, 152 | "text": {}, 153 | "textMode": "auto" 154 | }, 155 | "pluginVersion": "8.0.6", 156 | "targets": [ 157 | { 158 | "exemplar": true, 159 | "expr": "cpu", 160 | "interval": "", 161 | "legendFormat": "", 162 | "refId": "A" 163 | } 164 | ], 165 | "title": "CPU", 166 | "type": "stat" 167 | }, 168 | { 169 | "datasource": null, 170 | "fieldConfig": { 171 | "defaults": { 172 | "color": { 173 | "mode": "palette-classic" 174 | }, 175 | "custom": { 176 | "axisLabel": "", 177 | "axisPlacement": "auto", 178 | "barAlignment": 0, 179 | "drawStyle": "line", 180 | "fillOpacity": 20, 181 | "gradientMode": "opacity", 182 | "hideFrom": { 183 | "legend": false, 184 | "tooltip": false, 185 | "viz": false 186 | }, 187 | "lineInterpolation": "smooth", 188 | "lineStyle": { 189 | "fill": "solid" 190 | }, 191 | "lineWidth": 1, 192 | "pointSize": 5, 193 | "scaleDistribution": { 194 | "type": "linear" 195 | }, 196 | "showPoints": "auto", 197 | "spanNulls": false, 198 | "stacking": { 199 | "group": "A", 200 | "mode": "none" 201 | }, 202 | "thresholdsStyle": { 203 | "mode": "off" 204 | } 205 | }, 206 | "mappings": [], 207 | "min": 0, 208 | "thresholds": { 209 | "mode": "absolute", 210 | "steps": [ 211 | { 212 | "color": "green", 213 | "value": null 214 | } 215 | ] 216 | }, 217 | "unit": "decbytes" 218 | }, 219 | "overrides": [ 220 | { 221 | "matcher": { 222 | "id": "byName", 223 | "options": "Total Memory" 224 | }, 225 | "properties": [ 226 | { 227 | "id": "color", 228 | "value": { 229 | "fixedColor": "semi-dark-yellow", 230 | "mode": "fixed" 231 | } 232 | } 233 | ] 234 | }, 235 | { 236 | "matcher": { 237 | "id": "byName", 238 | "options": "Memory Used" 239 | }, 240 | "properties": [ 241 | { 242 | "id": "color", 243 | "value": { 244 | "fixedColor": "green", 245 | "mode": "fixed" 246 | } 247 | } 248 | ] 249 | } 250 | ] 251 | }, 252 | "gridPos": { 253 | "h": 7, 254 | "w": 19, 255 | "x": 0, 256 | "y": 7 257 | }, 258 | "id": 11, 259 | "options": { 260 | "legend": { 261 | "calcs": [], 262 | "displayMode": "list", 263 | "placement": "bottom" 264 | }, 265 | "tooltip": { 266 | "mode": "multi" 267 | } 268 | }, 269 | "targets": [ 270 | { 271 | "exemplar": true, 272 | "expr": "total_memory - free_memory", 273 | "hide": false, 274 | "interval": "", 275 | "legendFormat": "Memory Used", 276 | "refId": "A" 277 | }, 278 | { 279 | "exemplar": true, 280 | "expr": "total_memory", 281 | "instant": false, 282 | "interval": "", 283 | "intervalFactor": 1, 284 | "legendFormat": "Total Memory", 285 | "refId": "B" 286 | } 287 | ], 288 | "title": "Memory Usage", 289 | "type": "timeseries" 290 | }, 291 | { 292 | "datasource": null, 293 | "fieldConfig": { 294 | "defaults": { 295 | "color": { 296 | "mode": "thresholds" 297 | }, 298 | "mappings": [], 299 | "thresholds": { 300 | "mode": "percentage", 301 | "steps": [ 302 | { 303 | "color": "green", 304 | "value": null 305 | } 306 | ] 307 | }, 308 | "unit": "decbytes" 309 | }, 310 | "overrides": [] 311 | }, 312 | "gridPos": { 313 | "h": 7, 314 | "w": 5, 315 | "x": 19, 316 | "y": 7 317 | }, 318 | "id": 20, 319 | "options": { 320 | "colorMode": "value", 321 | "graphMode": "none", 322 | "justifyMode": "auto", 323 | "orientation": "auto", 324 | "reduceOptions": { 325 | "calcs": [ 326 | "lastNotNull" 327 | ], 328 | "fields": "", 329 | "values": false 330 | }, 331 | "text": {}, 332 | "textMode": "auto" 333 | }, 334 | "pluginVersion": "8.0.6", 335 | "targets": [ 336 | { 337 | "exemplar": true, 338 | "expr": "total_memory - free_memory", 339 | "interval": "", 340 | "legendFormat": "Memory", 341 | "refId": "A" 342 | } 343 | ], 344 | "title": "Memory", 345 | "type": "stat" 346 | }, 347 | { 348 | "datasource": null, 349 | "fieldConfig": { 350 | "defaults": { 351 | "color": { 352 | "mode": "palette-classic" 353 | }, 354 | "custom": { 355 | "axisLabel": "", 356 | "axisPlacement": "auto", 357 | "axisSoftMax": 20, 358 | "barAlignment": 0, 359 | "drawStyle": "line", 360 | "fillOpacity": 20, 361 | "gradientMode": "opacity", 362 | "hideFrom": { 363 | "legend": false, 364 | "tooltip": false, 365 | "viz": false 366 | }, 367 | "lineInterpolation": "smooth", 368 | "lineWidth": 1, 369 | "pointSize": 5, 370 | "scaleDistribution": { 371 | "type": "linear" 372 | }, 373 | "showPoints": "auto", 374 | "spanNulls": false, 375 | "stacking": { 376 | "group": "A", 377 | "mode": "none" 378 | }, 379 | "thresholdsStyle": { 380 | "mode": "off" 381 | } 382 | }, 383 | "decimals": 0, 384 | "mappings": [], 385 | "min": 0, 386 | "thresholds": { 387 | "mode": "absolute", 388 | "steps": [ 389 | { 390 | "color": "green", 391 | "value": null 392 | } 393 | ] 394 | }, 395 | "unit": "TPS" 396 | }, 397 | "overrides": [] 398 | }, 399 | "gridPos": { 400 | "h": 7, 401 | "w": 19, 402 | "x": 0, 403 | "y": 14 404 | }, 405 | "id": 14, 406 | "options": { 407 | "legend": { 408 | "calcs": [], 409 | "displayMode": "hidden", 410 | "placement": "bottom" 411 | }, 412 | "tooltip": { 413 | "mode": "multi" 414 | } 415 | }, 416 | "targets": [ 417 | { 418 | "exemplar": true, 419 | "expr": "tps", 420 | "interval": "", 421 | "legendFormat": "TPS", 422 | "refId": "A" 423 | } 424 | ], 425 | "title": "TPS", 426 | "type": "timeseries" 427 | }, 428 | { 429 | "datasource": null, 430 | "fieldConfig": { 431 | "defaults": { 432 | "color": { 433 | "mode": "thresholds" 434 | }, 435 | "mappings": [], 436 | "thresholds": { 437 | "mode": "absolute", 438 | "steps": [ 439 | { 440 | "color": "green", 441 | "value": null 442 | } 443 | ] 444 | } 445 | }, 446 | "overrides": [] 447 | }, 448 | "gridPos": { 449 | "h": 7, 450 | "w": 5, 451 | "x": 19, 452 | "y": 14 453 | }, 454 | "id": 18, 455 | "options": { 456 | "colorMode": "value", 457 | "graphMode": "none", 458 | "justifyMode": "auto", 459 | "orientation": "auto", 460 | "reduceOptions": { 461 | "calcs": [ 462 | "lastNotNull" 463 | ], 464 | "fields": "", 465 | "values": false 466 | }, 467 | "text": {}, 468 | "textMode": "auto" 469 | }, 470 | "pluginVersion": "8.0.6", 471 | "targets": [ 472 | { 473 | "exemplar": true, 474 | "expr": "tps", 475 | "interval": "", 476 | "legendFormat": "TPS", 477 | "refId": "A" 478 | } 479 | ], 480 | "title": "TPS", 481 | "type": "stat" 482 | }, 483 | { 484 | "datasource": null, 485 | "fieldConfig": { 486 | "defaults": { 487 | "color": { 488 | "mode": "palette-classic" 489 | }, 490 | "custom": { 491 | "axisLabel": "", 492 | "axisPlacement": "auto", 493 | "barAlignment": 0, 494 | "drawStyle": "line", 495 | "fillOpacity": 20, 496 | "gradientMode": "opacity", 497 | "hideFrom": { 498 | "legend": false, 499 | "tooltip": false, 500 | "viz": false 501 | }, 502 | "lineInterpolation": "smooth", 503 | "lineWidth": 1, 504 | "pointSize": 5, 505 | "scaleDistribution": { 506 | "type": "linear" 507 | }, 508 | "showPoints": "auto", 509 | "spanNulls": false, 510 | "stacking": { 511 | "group": "A", 512 | "mode": "none" 513 | }, 514 | "thresholdsStyle": { 515 | "mode": "off" 516 | } 517 | }, 518 | "mappings": [], 519 | "thresholds": { 520 | "mode": "absolute", 521 | "steps": [ 522 | { 523 | "color": "green", 524 | "value": null 525 | } 526 | ] 527 | }, 528 | "unit": "ms" 529 | }, 530 | "overrides": [] 531 | }, 532 | "gridPos": { 533 | "h": 7, 534 | "w": 19, 535 | "x": 0, 536 | "y": 21 537 | }, 538 | "id": 16, 539 | "options": { 540 | "legend": { 541 | "calcs": [], 542 | "displayMode": "hidden", 543 | "placement": "bottom" 544 | }, 545 | "tooltip": { 546 | "mode": "single" 547 | } 548 | }, 549 | "targets": [ 550 | { 551 | "exemplar": true, 552 | "expr": "mspt", 553 | "interval": "", 554 | "legendFormat": "MSPT", 555 | "refId": "A" 556 | } 557 | ], 558 | "title": "MSPT ", 559 | "type": "timeseries" 560 | }, 561 | { 562 | "datasource": null, 563 | "fieldConfig": { 564 | "defaults": { 565 | "color": { 566 | "mode": "thresholds" 567 | }, 568 | "mappings": [], 569 | "thresholds": { 570 | "mode": "absolute", 571 | "steps": [ 572 | { 573 | "color": "green", 574 | "value": null 575 | }, 576 | { 577 | "color": "yellow", 578 | "value": 40 579 | }, 580 | { 581 | "color": "red", 582 | "value": 50 583 | } 584 | ] 585 | } 586 | }, 587 | "overrides": [] 588 | }, 589 | "gridPos": { 590 | "h": 7, 591 | "w": 5, 592 | "x": 19, 593 | "y": 21 594 | }, 595 | "id": 22, 596 | "options": { 597 | "colorMode": "value", 598 | "graphMode": "none", 599 | "justifyMode": "auto", 600 | "orientation": "auto", 601 | "reduceOptions": { 602 | "calcs": [ 603 | "lastNotNull" 604 | ], 605 | "fields": "", 606 | "values": false 607 | }, 608 | "text": {}, 609 | "textMode": "auto" 610 | }, 611 | "pluginVersion": "8.0.6", 612 | "targets": [ 613 | { 614 | "exemplar": true, 615 | "expr": "mspt", 616 | "interval": "", 617 | "legendFormat": "mspt", 618 | "refId": "A" 619 | } 620 | ], 621 | "title": "MSPT", 622 | "type": "stat" 623 | }, 624 | { 625 | "datasource": null, 626 | "fieldConfig": { 627 | "defaults": { 628 | "color": { 629 | "mode": "palette-classic" 630 | }, 631 | "custom": { 632 | "axisLabel": "", 633 | "axisPlacement": "auto", 634 | "barAlignment": 0, 635 | "drawStyle": "line", 636 | "fillOpacity": 20, 637 | "gradientMode": "opacity", 638 | "hideFrom": { 639 | "legend": false, 640 | "tooltip": false, 641 | "viz": false 642 | }, 643 | "lineInterpolation": "smooth", 644 | "lineStyle": { 645 | "fill": "solid" 646 | }, 647 | "lineWidth": 1, 648 | "pointSize": 5, 649 | "scaleDistribution": { 650 | "type": "linear" 651 | }, 652 | "showPoints": "auto", 653 | "spanNulls": false, 654 | "stacking": { 655 | "group": "A", 656 | "mode": "none" 657 | }, 658 | "thresholdsStyle": { 659 | "mode": "off" 660 | } 661 | }, 662 | "decimals": 0, 663 | "mappings": [], 664 | "thresholds": { 665 | "mode": "absolute", 666 | "steps": [ 667 | { 668 | "color": "green", 669 | "value": null 670 | } 671 | ] 672 | }, 673 | "unit": "none" 674 | }, 675 | "overrides": [] 676 | }, 677 | "gridPos": { 678 | "h": 7, 679 | "w": 19, 680 | "x": 0, 681 | "y": 28 682 | }, 683 | "id": 2, 684 | "options": { 685 | "legend": { 686 | "calcs": [], 687 | "displayMode": "hidden", 688 | "placement": "bottom" 689 | }, 690 | "tooltip": { 691 | "mode": "single" 692 | } 693 | }, 694 | "targets": [ 695 | { 696 | "exemplar": true, 697 | "expr": "player_count", 698 | "interval": "", 699 | "legendFormat": "Players", 700 | "refId": "A" 701 | } 702 | ], 703 | "title": "Player Count", 704 | "type": "timeseries" 705 | }, 706 | { 707 | "datasource": null, 708 | "fieldConfig": { 709 | "defaults": { 710 | "color": { 711 | "mode": "thresholds" 712 | }, 713 | "mappings": [], 714 | "thresholds": { 715 | "mode": "absolute", 716 | "steps": [ 717 | { 718 | "color": "green", 719 | "value": null 720 | } 721 | ] 722 | } 723 | }, 724 | "overrides": [] 725 | }, 726 | "gridPos": { 727 | "h": 7, 728 | "w": 5, 729 | "x": 19, 730 | "y": 28 731 | }, 732 | "id": 4, 733 | "options": { 734 | "colorMode": "value", 735 | "graphMode": "none", 736 | "justifyMode": "auto", 737 | "orientation": "auto", 738 | "reduceOptions": { 739 | "calcs": [ 740 | "lastNotNull" 741 | ], 742 | "fields": "", 743 | "values": false 744 | }, 745 | "text": {}, 746 | "textMode": "auto" 747 | }, 748 | "pluginVersion": "8.0.6", 749 | "targets": [ 750 | { 751 | "exemplar": true, 752 | "expr": "player_count", 753 | "interval": "", 754 | "legendFormat": "", 755 | "refId": "A" 756 | } 757 | ], 758 | "title": "Player Count", 759 | "type": "stat" 760 | }, 761 | { 762 | "datasource": null, 763 | "fieldConfig": { 764 | "defaults": { 765 | "color": { 766 | "mode": "palette-classic" 767 | }, 768 | "custom": { 769 | "axisLabel": "", 770 | "axisPlacement": "auto", 771 | "barAlignment": 0, 772 | "drawStyle": "line", 773 | "fillOpacity": 20, 774 | "gradientMode": "opacity", 775 | "hideFrom": { 776 | "legend": false, 777 | "tooltip": false, 778 | "viz": false 779 | }, 780 | "lineInterpolation": "smooth", 781 | "lineWidth": 1, 782 | "pointSize": 5, 783 | "scaleDistribution": { 784 | "type": "linear" 785 | }, 786 | "showPoints": "auto", 787 | "spanNulls": false, 788 | "stacking": { 789 | "group": "A", 790 | "mode": "none" 791 | }, 792 | "thresholdsStyle": { 793 | "mode": "off" 794 | } 795 | }, 796 | "mappings": [], 797 | "thresholds": { 798 | "mode": "absolute", 799 | "steps": [ 800 | { 801 | "color": "green", 802 | "value": null 803 | } 804 | ] 805 | } 806 | }, 807 | "overrides": [ 808 | { 809 | "matcher": { 810 | "id": "byName", 811 | "options": "Overworld" 812 | }, 813 | "properties": [ 814 | { 815 | "id": "color", 816 | "value": { 817 | "fixedColor": "green", 818 | "mode": "fixed" 819 | } 820 | } 821 | ] 822 | }, 823 | { 824 | "matcher": { 825 | "id": "byName", 826 | "options": "The End" 827 | }, 828 | "properties": [ 829 | { 830 | "id": "color", 831 | "value": { 832 | "fixedColor": "yellow", 833 | "mode": "fixed" 834 | } 835 | } 836 | ] 837 | }, 838 | { 839 | "matcher": { 840 | "id": "byName", 841 | "options": "The Nether" 842 | }, 843 | "properties": [ 844 | { 845 | "id": "color", 846 | "value": { 847 | "fixedColor": "semi-dark-red", 848 | "mode": "fixed" 849 | } 850 | } 851 | ] 852 | } 853 | ] 854 | }, 855 | "gridPos": { 856 | "h": 7, 857 | "w": 19, 858 | "x": 0, 859 | "y": 35 860 | }, 861 | "id": 10, 862 | "options": { 863 | "legend": { 864 | "calcs": [], 865 | "displayMode": "list", 866 | "placement": "bottom" 867 | }, 868 | "tooltip": { 869 | "mode": "multi" 870 | } 871 | }, 872 | "targets": [ 873 | { 874 | "exemplar": true, 875 | "expr": "entity_count_world", 876 | "interval": "", 877 | "legendFormat": "{{world}}", 878 | "refId": "A" 879 | } 880 | ], 881 | "title": "Entity Count", 882 | "transformations": [ 883 | { 884 | "id": "renameByRegex", 885 | "options": { 886 | "regex": "world_the_nether", 887 | "renamePattern": "The Nether" 888 | } 889 | }, 890 | { 891 | "id": "renameByRegex", 892 | "options": { 893 | "regex": "world_the_end", 894 | "renamePattern": "The End" 895 | } 896 | }, 897 | { 898 | "id": "renameByRegex", 899 | "options": { 900 | "regex": "world", 901 | "renamePattern": "Overworld" 902 | } 903 | } 904 | ], 905 | "type": "timeseries" 906 | }, 907 | { 908 | "datasource": null, 909 | "fieldConfig": { 910 | "defaults": { 911 | "color": { 912 | "mode": "thresholds" 913 | }, 914 | "mappings": [], 915 | "thresholds": { 916 | "mode": "absolute", 917 | "steps": [ 918 | { 919 | "color": "green", 920 | "value": null 921 | } 922 | ] 923 | } 924 | }, 925 | "overrides": [] 926 | }, 927 | "gridPos": { 928 | "h": 7, 929 | "w": 5, 930 | "x": 19, 931 | "y": 35 932 | }, 933 | "id": 24, 934 | "options": { 935 | "colorMode": "value", 936 | "graphMode": "none", 937 | "justifyMode": "auto", 938 | "orientation": "auto", 939 | "reduceOptions": { 940 | "calcs": [ 941 | "lastNotNull" 942 | ], 943 | "fields": "", 944 | "values": false 945 | }, 946 | "text": {}, 947 | "textMode": "auto" 948 | }, 949 | "pluginVersion": "8.0.6", 950 | "targets": [ 951 | { 952 | "exemplar": true, 953 | "expr": "entity_count", 954 | "interval": "", 955 | "legendFormat": "", 956 | "refId": "A" 957 | } 958 | ], 959 | "title": "Entity Count", 960 | "type": "stat" 961 | }, 962 | { 963 | "datasource": null, 964 | "fieldConfig": { 965 | "defaults": { 966 | "color": { 967 | "mode": "palette-classic" 968 | }, 969 | "custom": { 970 | "axisLabel": "", 971 | "axisPlacement": "auto", 972 | "barAlignment": 0, 973 | "drawStyle": "line", 974 | "fillOpacity": 20, 975 | "gradientMode": "opacity", 976 | "hideFrom": { 977 | "legend": false, 978 | "tooltip": false, 979 | "viz": false 980 | }, 981 | "lineInterpolation": "smooth", 982 | "lineStyle": { 983 | "fill": "solid" 984 | }, 985 | "lineWidth": 1, 986 | "pointSize": 5, 987 | "scaleDistribution": { 988 | "type": "linear" 989 | }, 990 | "showPoints": "auto", 991 | "spanNulls": false, 992 | "stacking": { 993 | "group": "A", 994 | "mode": "none" 995 | }, 996 | "thresholdsStyle": { 997 | "mode": "off" 998 | } 999 | }, 1000 | "mappings": [], 1001 | "thresholds": { 1002 | "mode": "absolute", 1003 | "steps": [ 1004 | { 1005 | "color": "green", 1006 | "value": null 1007 | } 1008 | ] 1009 | } 1010 | }, 1011 | "overrides": [ 1012 | { 1013 | "matcher": { 1014 | "id": "byName", 1015 | "options": "The End" 1016 | }, 1017 | "properties": [ 1018 | { 1019 | "id": "color", 1020 | "value": { 1021 | "fixedColor": "yellow", 1022 | "mode": "fixed" 1023 | } 1024 | } 1025 | ] 1026 | }, 1027 | { 1028 | "matcher": { 1029 | "id": "byName", 1030 | "options": "The Nether" 1031 | }, 1032 | "properties": [ 1033 | { 1034 | "id": "color", 1035 | "value": { 1036 | "fixedColor": "semi-dark-red", 1037 | "mode": "fixed" 1038 | } 1039 | } 1040 | ] 1041 | } 1042 | ] 1043 | }, 1044 | "gridPos": { 1045 | "h": 7, 1046 | "w": 19, 1047 | "x": 0, 1048 | "y": 42 1049 | }, 1050 | "id": 28, 1051 | "options": { 1052 | "legend": { 1053 | "calcs": [], 1054 | "displayMode": "list", 1055 | "placement": "bottom" 1056 | }, 1057 | "tooltip": { 1058 | "mode": "multi" 1059 | } 1060 | }, 1061 | "targets": [ 1062 | { 1063 | "exemplar": true, 1064 | "expr": "loaded_chunks_world", 1065 | "interval": "", 1066 | "legendFormat": "{{world}}", 1067 | "refId": "A" 1068 | } 1069 | ], 1070 | "title": "Loaded Chunks", 1071 | "transformations": [ 1072 | { 1073 | "id": "renameByRegex", 1074 | "options": { 1075 | "regex": "world_the_end", 1076 | "renamePattern": "The End" 1077 | } 1078 | }, 1079 | { 1080 | "id": "renameByRegex", 1081 | "options": { 1082 | "regex": "world_the_nether", 1083 | "renamePattern": "The Nether" 1084 | } 1085 | }, 1086 | { 1087 | "id": "renameByRegex", 1088 | "options": { 1089 | "regex": "world", 1090 | "renamePattern": "Overworld" 1091 | } 1092 | } 1093 | ], 1094 | "type": "timeseries" 1095 | }, 1096 | { 1097 | "datasource": null, 1098 | "fieldConfig": { 1099 | "defaults": { 1100 | "color": { 1101 | "mode": "thresholds" 1102 | }, 1103 | "mappings": [], 1104 | "thresholds": { 1105 | "mode": "absolute", 1106 | "steps": [ 1107 | { 1108 | "color": "green", 1109 | "value": null 1110 | } 1111 | ] 1112 | } 1113 | }, 1114 | "overrides": [] 1115 | }, 1116 | "gridPos": { 1117 | "h": 7, 1118 | "w": 5, 1119 | "x": 19, 1120 | "y": 42 1121 | }, 1122 | "id": 26, 1123 | "options": { 1124 | "colorMode": "value", 1125 | "graphMode": "none", 1126 | "justifyMode": "auto", 1127 | "orientation": "auto", 1128 | "reduceOptions": { 1129 | "calcs": [ 1130 | "lastNotNull" 1131 | ], 1132 | "fields": "", 1133 | "values": false 1134 | }, 1135 | "text": {}, 1136 | "textMode": "auto" 1137 | }, 1138 | "pluginVersion": "8.0.6", 1139 | "targets": [ 1140 | { 1141 | "exemplar": true, 1142 | "expr": "loaded_chunks", 1143 | "interval": "", 1144 | "legendFormat": "", 1145 | "refId": "A" 1146 | } 1147 | ], 1148 | "title": "Loaded Chunks", 1149 | "type": "stat" 1150 | }, 1151 | { 1152 | "datasource": null, 1153 | "fieldConfig": { 1154 | "defaults": { 1155 | "color": { 1156 | "mode": "palette-classic" 1157 | }, 1158 | "custom": { 1159 | "axisLabel": "", 1160 | "axisPlacement": "auto", 1161 | "barAlignment": 0, 1162 | "drawStyle": "line", 1163 | "fillOpacity": 20, 1164 | "gradientMode": "opacity", 1165 | "hideFrom": { 1166 | "legend": false, 1167 | "tooltip": false, 1168 | "viz": false 1169 | }, 1170 | "lineInterpolation": "smooth", 1171 | "lineStyle": { 1172 | "fill": "solid" 1173 | }, 1174 | "lineWidth": 1, 1175 | "pointSize": 5, 1176 | "scaleDistribution": { 1177 | "type": "linear" 1178 | }, 1179 | "showPoints": "auto", 1180 | "spanNulls": false, 1181 | "stacking": { 1182 | "group": "A", 1183 | "mode": "none" 1184 | }, 1185 | "thresholdsStyle": { 1186 | "mode": "off" 1187 | } 1188 | }, 1189 | "mappings": [], 1190 | "min": 0, 1191 | "thresholds": { 1192 | "mode": "absolute", 1193 | "steps": [ 1194 | { 1195 | "color": "green", 1196 | "value": null 1197 | } 1198 | ] 1199 | }, 1200 | "unit": "decbytes" 1201 | }, 1202 | "overrides": [ 1203 | { 1204 | "matcher": { 1205 | "id": "byName", 1206 | "options": "The Nether" 1207 | }, 1208 | "properties": [ 1209 | { 1210 | "id": "color", 1211 | "value": { 1212 | "fixedColor": "semi-dark-red", 1213 | "mode": "fixed" 1214 | } 1215 | } 1216 | ] 1217 | } 1218 | ] 1219 | }, 1220 | "gridPos": { 1221 | "h": 7, 1222 | "w": 19, 1223 | "x": 0, 1224 | "y": 49 1225 | }, 1226 | "id": 34, 1227 | "options": { 1228 | "legend": { 1229 | "calcs": [], 1230 | "displayMode": "list", 1231 | "placement": "bottom" 1232 | }, 1233 | "tooltip": { 1234 | "mode": "multi" 1235 | } 1236 | }, 1237 | "targets": [ 1238 | { 1239 | "exemplar": true, 1240 | "expr": "disk_space_world", 1241 | "interval": "", 1242 | "legendFormat": "{{world}}", 1243 | "refId": "A" 1244 | } 1245 | ], 1246 | "title": "Disk Usage", 1247 | "transformations": [ 1248 | { 1249 | "id": "renameByRegex", 1250 | "options": { 1251 | "regex": "world_the_nether", 1252 | "renamePattern": "The Nether" 1253 | } 1254 | }, 1255 | { 1256 | "id": "renameByRegex", 1257 | "options": { 1258 | "regex": "world_the_end", 1259 | "renamePattern": "The End" 1260 | } 1261 | }, 1262 | { 1263 | "id": "renameByRegex", 1264 | "options": { 1265 | "regex": "world", 1266 | "renamePattern": "Overworld" 1267 | } 1268 | } 1269 | ], 1270 | "type": "timeseries" 1271 | }, 1272 | { 1273 | "datasource": null, 1274 | "fieldConfig": { 1275 | "defaults": { 1276 | "color": { 1277 | "mode": "thresholds" 1278 | }, 1279 | "mappings": [], 1280 | "max": 50000000000, 1281 | "thresholds": { 1282 | "mode": "percentage", 1283 | "steps": [ 1284 | { 1285 | "color": "green", 1286 | "value": null 1287 | }, 1288 | { 1289 | "color": "#EAB839", 1290 | "value": 50 1291 | }, 1292 | { 1293 | "color": "red", 1294 | "value": 80 1295 | } 1296 | ] 1297 | }, 1298 | "unit": "decbytes" 1299 | }, 1300 | "overrides": [] 1301 | }, 1302 | "gridPos": { 1303 | "h": 7, 1304 | "w": 5, 1305 | "x": 19, 1306 | "y": 49 1307 | }, 1308 | "id": 36, 1309 | "options": { 1310 | "colorMode": "value", 1311 | "graphMode": "none", 1312 | "justifyMode": "auto", 1313 | "orientation": "auto", 1314 | "reduceOptions": { 1315 | "calcs": [ 1316 | "lastNotNull" 1317 | ], 1318 | "fields": "", 1319 | "values": false 1320 | }, 1321 | "text": {}, 1322 | "textMode": "auto" 1323 | }, 1324 | "pluginVersion": "8.0.6", 1325 | "targets": [ 1326 | { 1327 | "exemplar": true, 1328 | "expr": "sum(disk_space_world)", 1329 | "interval": "", 1330 | "legendFormat": "", 1331 | "refId": "A" 1332 | } 1333 | ], 1334 | "title": "Disk Usage", 1335 | "type": "stat" 1336 | } 1337 | ], 1338 | "refresh": "15s", 1339 | "schemaVersion": 30, 1340 | "style": "dark", 1341 | "tags": [], 1342 | "templating": { 1343 | "list": [] 1344 | }, 1345 | "time": { 1346 | "from": "now-15m", 1347 | "to": "now" 1348 | }, 1349 | "timepicker": { 1350 | "refresh_intervals": [ 1351 | "5s", 1352 | "10s", 1353 | "15s", 1354 | "30s", 1355 | "1m", 1356 | "5m", 1357 | "15m", 1358 | "30m", 1359 | "1h", 1360 | "2h", 1361 | "1d" 1362 | ] 1363 | }, 1364 | "timezone": "", 1365 | "title": "Minecraft Stats", 1366 | "uid": "GtgaQ1znk", 1367 | "version": 60 1368 | } -------------------------------------------------------------------------------- /example-grafana-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diced/ServerStats/2a9d6a1f59d5c16a0d7ab9889c58cbeeab5d3246/example-grafana-dashboard.png -------------------------------------------------------------------------------- /fabric/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '0.10-SNAPSHOT' 3 | id 'com.github.johnrengelman.shadow' 4 | } 5 | 6 | archivesBaseName = "ServerStats-Fabric" 7 | version = project.mod_version 8 | group = project.mod_group + '.fabric' 9 | 10 | repositories { 11 | maven { url "https://mvnrepository.com/artifact/io.prometheus" } 12 | maven { url 'https://masa.dy.fi/maven' } 13 | } 14 | 15 | dependencies { 16 | minecraft "com.mojang:minecraft:${project.fabric_mc}" 17 | mappings "net.fabricmc:yarn:${project.fabric_yarn}:v2" 18 | modImplementation "net.fabricmc:fabric-loader:${project.fabric_loader}" 19 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 20 | modImplementation "carpet:fabric-carpet:${project.fabric_mc}-${project.fabric_carpet}" 21 | 22 | implementation 'net.kyori:adventure-api:4.8.1' 23 | implementation 'net.kyori:adventure-text-serializer-gson:4.7.0' 24 | implementation project(':common') 25 | 26 | shadow project(':common') 27 | shadow 'net.kyori:adventure-api:4.8.1' 28 | shadow 'net.kyori:adventure-text-serializer-gson:4.7.0' 29 | } 30 | 31 | 32 | processResources { 33 | inputs.property "version", project.version 34 | 35 | filesMatching("fabric.mod.json") { 36 | expand "version": project.version 37 | } 38 | } 39 | 40 | tasks.withType(JavaCompile).configureEach { 41 | it.options.encoding = "UTF-8" 42 | it.options.release = 17 43 | } 44 | 45 | java { 46 | withSourcesJar() 47 | } 48 | 49 | shadowJar { 50 | configurations = [project.configurations.shadow] 51 | classifier 'shadow-dev' 52 | minimize() 53 | } 54 | 55 | import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation 56 | 57 | task relocateShadowJar(type: ConfigureShadowRelocation) { 58 | target = tasks.shadowJar 59 | prefix = "${project.group}.${project.archivesBaseName}.shadow" 60 | } 61 | tasks.shadowJar.dependsOn tasks.relocateShadowJar 62 | 63 | remapJar { 64 | dependsOn shadowJar 65 | input.set shadowJar.archiveFile.get() 66 | } 67 | 68 | jar { 69 | from("LICENSE") { 70 | rename { "${it}_${project.archivesBaseName}"} 71 | } 72 | } -------------------------------------------------------------------------------- /fabric/src/main/java/me/diced/serverstats/fabric/FabricMetadata.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.fabric; 2 | 3 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 4 | import me.diced.serverstats.common.plugin.ServerStatsType; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import net.fabricmc.loader.api.metadata.ModMetadata; 7 | 8 | public class FabricMetadata implements ServerStatsMetadata { 9 | private final ModMetadata meta = FabricLoader.getInstance().getModContainer("serverstats").get().getMetadata(); 10 | 11 | @Override 12 | public ServerStatsType getType() { 13 | return ServerStatsType.FABRIC; 14 | } 15 | 16 | @Override 17 | public String getVersion() { 18 | return this.meta.getVersion().getFriendlyString(); 19 | } 20 | 21 | @Override 22 | public String getAuthor() { 23 | return this.meta.getAuthors().stream().findFirst().get().getName(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /fabric/src/main/java/me/diced/serverstats/fabric/FabricMetricsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.fabric; 2 | 3 | import carpet.helpers.TickSpeed; 4 | import carpet.logging.logHelpers.PacketCounter; 5 | import me.diced.serverstats.common.prometheus.MetricsManager; 6 | import net.minecraft.util.math.MathHelper; 7 | 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.TreeMap; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | public class FabricMetricsManager extends MetricsManager { 14 | private final FabricServerStats platform; 15 | 16 | public FabricMetricsManager(FabricServerStats platform) { 17 | super(platform.getServerStats()); 18 | this.platform = platform; 19 | } 20 | 21 | @Override 22 | public int getPlayerCount() { 23 | return this.platform.server.getCurrentPlayerCount(); 24 | } 25 | 26 | @Override 27 | public double getMspt() { 28 | return MathHelper.average(this.platform.server.lastTickLengths) * 1.0E-6D; 29 | } 30 | 31 | @Override 32 | public double getTps() { 33 | return 1000.0D / Math.max((TickSpeed.time_warp_start_time != 0) ? 0.0 : TickSpeed.mspt, this.getMspt()); 34 | } 35 | 36 | @Override 37 | public int getLoadedChunks() { 38 | AtomicInteger loadedChunks = new AtomicInteger(); 39 | 40 | this.platform.server.getWorlds().forEach(w -> loadedChunks.addAndGet(w.getChunkManager().getLoadedChunkCount())); 41 | 42 | return loadedChunks.intValue(); 43 | } 44 | 45 | @Override 46 | public int getEntityCount() { 47 | AtomicInteger entityCount = new AtomicInteger(); 48 | 49 | this.platform.server.getWorlds().forEach(w -> w.iterateEntities().forEach(e -> entityCount.incrementAndGet())); 50 | 51 | return entityCount.intValue(); 52 | } 53 | 54 | @Override 55 | public long getPacketRx() { 56 | return PacketCounter.totalIn; 57 | } 58 | 59 | @Override 60 | public long getPacketTx() { 61 | return PacketCounter.totalOut; 62 | } 63 | 64 | @Override 65 | public TreeMap getLoadedChunksPerWorld() { 66 | TreeMap worlds = new TreeMap<>(); 67 | 68 | this.platform.server.getWorlds().forEach(w -> worlds.put(this.platform.getLevelName(w), w.getChunkManager().getLoadedChunkCount())); 69 | 70 | return worlds; 71 | } 72 | 73 | @Override 74 | public TreeMap getEntityCountPerWorld() { 75 | TreeMap entities = new TreeMap<>(); 76 | 77 | this.platform.server.getWorlds().forEach(w -> { 78 | AtomicInteger in = new AtomicInteger(); 79 | w.iterateEntities().forEach(e -> in.getAndIncrement()); 80 | 81 | entities.put(this.platform.getLevelName(w), in.intValue()); 82 | }); 83 | 84 | return entities; 85 | } 86 | 87 | @Override 88 | public TreeMap getWorldPaths() { 89 | TreeMap paths = new TreeMap<>(); 90 | 91 | this.platform.server.getWorlds().forEach(w -> paths.put(this.platform.getLevelName(w), this.platform.levelNameToPath(w))); 92 | 93 | return paths; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /fabric/src/main/java/me/diced/serverstats/fabric/FabricServerStats.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.fabric; 2 | 3 | import carpet.logging.logHelpers.PacketCounter; 4 | import me.diced.serverstats.common.plugin.LogWrapper; 5 | import me.diced.serverstats.common.plugin.ServerStats; 6 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 7 | import me.diced.serverstats.common.plugin.ServerStatsPlatform; 8 | import me.diced.serverstats.common.prometheus.MetricsManager; 9 | import me.diced.serverstats.common.scheduler.Scheduler; 10 | import me.diced.serverstats.common.scheduler.ThreadScheduler; 11 | import me.diced.serverstats.fabric.command.FabricCommandExecutor; 12 | import net.fabricmc.api.ModInitializer; 13 | import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; 14 | import net.fabricmc.loader.api.FabricLoader; 15 | import net.minecraft.server.MinecraftServer; 16 | import net.minecraft.server.world.ServerWorld; 17 | import net.minecraft.util.Identifier; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.io.IOException; 22 | import java.nio.file.Path; 23 | import java.nio.file.Paths; 24 | 25 | public class FabricServerStats implements ModInitializer, ServerStatsPlatform { 26 | public MinecraftServer server; 27 | private ServerStats serverStats; 28 | private ThreadScheduler scheduler; 29 | private final FabricMetadata meta = new FabricMetadata(); 30 | private FabricMetricsManager metricsManager; 31 | 32 | @Override 33 | public void onInitialize() { 34 | try { 35 | 36 | this.scheduler = new ThreadScheduler(); 37 | LogWrapper logger = new LogWrapper() { 38 | private final Logger logger = LoggerFactory.getLogger("ServerStats"); 39 | 40 | @Override 41 | public void info(String msg) { 42 | this.logger.info(msg); 43 | } 44 | 45 | @Override 46 | public void error(String msg) { 47 | this.logger.error(msg); 48 | } 49 | }; 50 | 51 | this.serverStats = new ServerStats(this, logger); 52 | this.metricsManager = new FabricMetricsManager(this); 53 | new FabricCommandExecutor(this); 54 | 55 | this.start(); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | @Override 62 | public Path getConfigPath() { 63 | return FabricLoader.getInstance().getConfigDir().resolve("serverstats.conf"); 64 | } 65 | 66 | @Override 67 | public ServerStatsMetadata getMetadata() { 68 | return this.meta; 69 | } 70 | 71 | @Override 72 | public ServerStats getServerStats() { 73 | return this.serverStats; 74 | } 75 | 76 | @Override 77 | public Scheduler getScheduler() { 78 | return this.scheduler; 79 | } 80 | 81 | @Override 82 | public MetricsManager getMetricsManager() { 83 | return this.metricsManager; 84 | } 85 | 86 | @Override 87 | public void start() { 88 | ServerLifecycleEvents.SERVER_STARTED.register(server -> { 89 | this.server = server; 90 | 91 | this.serverStats.tasks.register(); 92 | }); 93 | 94 | ServerLifecycleEvents.SERVER_STOPPED.register(s -> this.serverStats.stop()); 95 | } 96 | 97 | public String getLevelName(ServerWorld world) { 98 | return switch (world.getRegistryKey().getValue().getPath()) { 99 | case "the_nether" -> "world_the_nether"; 100 | case "the_end" -> "world_the_end"; 101 | default -> "world"; 102 | }; 103 | } 104 | 105 | public Path levelNameToPath(ServerWorld world) { 106 | return switch (this.getLevelName(world)) { 107 | case "world_the_nether" -> Paths.get(".", "world", "DIM-1"); 108 | case "world_the_end" -> Paths.get(".", "world", "DIM1"); 109 | default -> Paths.get(".", "world"); 110 | }; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /fabric/src/main/java/me/diced/serverstats/fabric/command/FabricCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.fabric.command; 2 | 3 | import com.mojang.brigadier.Command; 4 | import com.mojang.brigadier.context.CommandContext; 5 | import com.mojang.brigadier.suggestion.SuggestionProvider; 6 | import com.mojang.brigadier.suggestion.Suggestions; 7 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 8 | import com.mojang.brigadier.tree.ArgumentCommandNode; 9 | import com.mojang.brigadier.tree.LiteralCommandNode; 10 | import me.diced.serverstats.common.plugin.ServerStats; 11 | import me.diced.serverstats.common.command.CommandExecutor; 12 | import me.diced.serverstats.fabric.FabricServerStats; 13 | import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; 14 | import net.minecraft.server.command.ServerCommandSource; 15 | 16 | import java.util.List; 17 | import java.util.concurrent.CompletableFuture; 18 | 19 | import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; 20 | import static me.diced.serverstats.common.plugin.Util.tokenize; 21 | import static net.minecraft.server.command.CommandManager.*; 22 | 23 | public class FabricCommandExecutor implements CommandExecutor, Command, SuggestionProvider { 24 | private String CMD_NAME = "stats"; 25 | public FabricServerStats platform; 26 | 27 | public FabricCommandExecutor(FabricServerStats platform) { 28 | this.platform = platform; 29 | 30 | CommandRegistrationCallback.EVENT.register(((dispatcher, dedicated) -> { 31 | LiteralCommandNode cmd = literal(CMD_NAME) 32 | .executes(this) 33 | .build(); 34 | 35 | ArgumentCommandNode args = argument("args", greedyString()) 36 | .suggests(this) 37 | .executes(this) 38 | .build(); 39 | 40 | cmd.addChild(args); 41 | dispatcher.getRoot().addChild(cmd); 42 | })); 43 | } 44 | 45 | 46 | @Override 47 | public int run(CommandContext context) { 48 | String input = context.getInput(); 49 | int start = context.getRange().getStart(); 50 | int removing = Math.min(start + CMD_NAME.length() + 1, input.length()); 51 | 52 | List arguments = tokenize(input.substring(removing)); 53 | 54 | FabricContext ctx = new FabricContext(context); 55 | this.executeCommand(arguments, ctx); 56 | return Command.SINGLE_SUCCESS; 57 | } 58 | 59 | @Override 60 | public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { 61 | FabricContext ctx = new FabricContext(context); 62 | FabricCompletionsManager completions = new FabricCompletionsManager(builder, ctx); 63 | 64 | completions.register(); 65 | 66 | return builder.buildFuture(); 67 | } 68 | 69 | @Override 70 | public ServerStats getPlatform() { 71 | return this.platform.getServerStats(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /fabric/src/main/java/me/diced/serverstats/fabric/command/FabricCompletionsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.fabric.command; 2 | 3 | import com.mojang.brigadier.suggestion.SuggestionsBuilder; 4 | import me.diced.serverstats.common.command.CompletionsManager; 5 | import me.diced.serverstats.common.command.Context; 6 | 7 | public class FabricCompletionsManager extends CompletionsManager { 8 | public FabricCompletionsManager(SuggestionsBuilder suggestionsBuilder, Context ctx) { 9 | super(suggestionsBuilder, ctx); 10 | } 11 | 12 | @Override 13 | public void suggest(String cmd) { 14 | this.suggester.suggest(cmd); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fabric/src/main/java/me/diced/serverstats/fabric/command/FabricContext.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.fabric.command; 2 | 3 | import com.mojang.brigadier.context.CommandContext; 4 | import me.diced.serverstats.common.command.Context; 5 | import net.kyori.adventure.text.Component; 6 | import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 7 | import net.minecraft.server.command.ServerCommandSource; 8 | import net.minecraft.text.Text; 9 | 10 | public class FabricContext implements Context { 11 | private final CommandContext sender; 12 | 13 | public FabricContext(CommandContext sender) { 14 | this.sender = sender; 15 | } 16 | 17 | public void sendMessage(Component message) { 18 | Text txt = Text.Serializer.fromJson(GsonComponentSerializer.gson().serialize(message)); 19 | 20 | this.sender.getSource().sendFeedback(txt, false); 21 | } 22 | 23 | public boolean isOp() { 24 | return this.sender.getSource().hasPermissionLevel(4); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "serverstats", 4 | "version": "${version}", 5 | "name": "ServerStats", 6 | "description": "Visualize your Minecraft server statistics in realtime", 7 | "authors": ["dicedtomato"], 8 | "contact": { 9 | "homepage": "https://serverstats.diced.me", 10 | "sources": "https://github.com/diced/ServerStats", 11 | "issues": "https://github.com/diced/ServerStats/issues" 12 | }, 13 | "license": "MIT", 14 | "icon": "", 15 | "environment": "server", 16 | "entrypoints": { 17 | "main": [ 18 | "me.diced.serverstats.fabric.FabricServerStats" 19 | ] 20 | }, 21 | "mixins": [], 22 | "depends": { 23 | "fabricloader": ">=0.11.6", 24 | "fabric": "*", 25 | "carpet": "*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # common mod 5 | mod_version = 1.2.8 6 | mod_group = me.diced.serverstats 7 | mod_desc = Visualize your Minecraft server statistics in realtime 8 | mod_author = dicedtomato 9 | 10 | # fabric 11 | fabric_mc = 1.17 12 | fabric_yarn = 1.17+build.13 13 | fabric_loader = 0.11.6 14 | fabric_version = 0.36.0+1.17 15 | fabric_carpet=1.4.40+v210608 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diced/ServerStats/2a9d6a1f59d5c16a0d7ab9889c58cbeeab5d3246/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | # vars 2 | version=$(cat gradle.properties | awk '/mod_version/ && sub(/^.{14}/,"",$0)') 3 | projects=("bukkit" "bungee" "fabric" "velocity") 4 | project_file_names=("ServerStats-Bukkit" "ServerStats-Bungee" "ServerStats-Fabric" "ServerStats-Velocity") 5 | 6 | [ -d "./builds" ] && rm -rf builds 7 | 8 | mkdir builds 9 | 10 | 11 | for ((i = 0; i < ${#projects[@]}; ++i )); do 12 | project=${projects[i]} 13 | project_file_name="${project_file_names[i]}-$version.jar" 14 | 15 | echo "./$project/build/libs/$project_file_name" 16 | 17 | echo -e "\n\n-------- Building - $project --------\n\n" 18 | ./gradlew ":$project:build" 19 | 20 | cp "./$project/build/libs/$project_file_name" ./builds 21 | done 22 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | # removes project specific build dir 2 | # removes root build dir 3 | # removes build.sh generated build dir 4 | rm -rf **/build build builds -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | // Fabric Needs this 2 | pluginManagement { 3 | repositories { 4 | maven { 5 | url 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | rootProject.name = 'serverstats' 12 | 13 | include ( 14 | 'common', 15 | // servers 16 | 'fabric', 17 | 'bukkit', 18 | 19 | // proxies 20 | 'bungee', 21 | 'velocity' 22 | ) -------------------------------------------------------------------------------- /velocity/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'net.kyori.blossom' version '1.2.0' 3 | id 'com.github.johnrengelman.shadow' 4 | } 5 | 6 | archivesBaseName = 'ServerStats-Velocity' 7 | version = project.mod_version 8 | group = project.mod_group + '.velocity' 9 | 10 | repositories { 11 | maven { url 'https://repo.velocitypowered.com/releases/' } 12 | } 13 | 14 | dependencies { 15 | implementation project(':common') 16 | 17 | compileOnly 'com.velocitypowered:velocity-api:1.1.5' 18 | annotationProcessor 'com.velocitypowered:velocity-api:1.1.5' 19 | } 20 | 21 | blossom { 22 | replaceToken '${version}', project.mod_version, 'src/main/java/me/diced/serverstats/velocity/VelocityServerStats.java' 23 | } 24 | 25 | shadowJar { 26 | archiveClassifier.set(null) 27 | minimize() 28 | 29 | relocate 'org.spongepowered.configurate', "${rootProject.group}.lib.org.spongepowered.configurate" 30 | relocate 'org.checkerframework', "${rootProject.group}.lib.org.checkerframework" 31 | 32 | } 33 | 34 | tasks.build.dependsOn tasks.shadowJar -------------------------------------------------------------------------------- /velocity/src/main/java/me/diced/serverstats/velocity/VelocityMetadata.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.velocity; 2 | 3 | import com.velocitypowered.api.plugin.PluginDescription; 4 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 5 | import me.diced.serverstats.common.plugin.ServerStatsType; 6 | 7 | public class VelocityMetadata implements ServerStatsMetadata { 8 | private final PluginDescription meta; 9 | 10 | public VelocityMetadata(PluginDescription meta) { 11 | this.meta = meta; 12 | } 13 | 14 | @Override 15 | public ServerStatsType getType() { 16 | return ServerStatsType.VELOCITY; 17 | } 18 | 19 | @Override 20 | public String getVersion() { 21 | return this.meta.getVersion().get(); 22 | } 23 | 24 | @Override 25 | public String getAuthor() { 26 | return this.meta.getAuthors().get(0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /velocity/src/main/java/me/diced/serverstats/velocity/VelocityMetricsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.velocity; 2 | 3 | import me.diced.serverstats.common.prometheus.MetricsManager; 4 | 5 | public class VelocityMetricsManager extends MetricsManager { 6 | private final VelocityServerStats platform; 7 | 8 | public VelocityMetricsManager(VelocityServerStats platform) { 9 | super(platform.getServerStats()); 10 | this.platform = platform; 11 | } 12 | 13 | @Override 14 | public int getPlayerCount() { 15 | return this.platform.server.getPlayerCount(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /velocity/src/main/java/me/diced/serverstats/velocity/VelocityScheduler.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.velocity; 2 | 3 | import com.velocitypowered.api.scheduler.ScheduledTask; 4 | import me.diced.serverstats.common.scheduler.Scheduler; 5 | import me.diced.serverstats.common.scheduler.Task; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class VelocityScheduler implements Scheduler { 10 | private final VelocityServerStats platform; 11 | private final com.velocitypowered.api.scheduler.Scheduler scheduler; 12 | public VelocityScheduler(VelocityServerStats platform) { 13 | this.platform = platform; 14 | this.scheduler = platform.server.getScheduler(); 15 | } 16 | 17 | @Override 18 | public Task scheduleRepeatingTask(Runnable task, long interval, TimeUnit unit) { 19 | ScheduledTask sct = this.scheduler.buildTask(this.platform, task).repeat(interval, unit).schedule(); 20 | return () -> sct.cancel(); 21 | } 22 | 23 | @Override 24 | public Task schedule(Runnable task) { 25 | ScheduledTask sct = this.scheduler.buildTask(this.platform, task).schedule(); 26 | return () -> sct.cancel(); 27 | } 28 | 29 | @Override 30 | public void stop() { 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /velocity/src/main/java/me/diced/serverstats/velocity/VelocityServerStats.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.velocity; 2 | 3 | import com.google.inject.Inject; 4 | import com.velocitypowered.api.event.Subscribe; 5 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 6 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 7 | import com.velocitypowered.api.plugin.Plugin; 8 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 9 | import com.velocitypowered.api.proxy.ProxyServer; 10 | import me.diced.serverstats.common.plugin.LogWrapper; 11 | import me.diced.serverstats.common.prometheus.MetricsManager; 12 | import me.diced.serverstats.common.plugin.ServerStats; 13 | import me.diced.serverstats.common.plugin.ServerStatsMetadata; 14 | import me.diced.serverstats.common.plugin.ServerStatsPlatform; 15 | import me.diced.serverstats.common.scheduler.Scheduler; 16 | 17 | import java.io.IOException; 18 | import java.nio.file.Path; 19 | 20 | import me.diced.serverstats.velocity.command.VelocityCommandExecutor; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | @Plugin(id = "serverstats", 25 | name = "ServerStats", 26 | version = "${version}", 27 | url = "https://serverstats.diced.me", 28 | description = "Visualize your Minecraft server statistics in realtime", 29 | authors = {"dicedtomato"} 30 | ) 31 | public class VelocityServerStats implements ServerStatsPlatform { 32 | private ServerStats serverStats; 33 | private VelocityScheduler scheduler; 34 | private VelocityMetadata meta; 35 | private VelocityMetricsManager metricsManager; 36 | private final LogWrapper logger; 37 | private final Path dataDirectory; 38 | public final ProxyServer server; 39 | 40 | @Inject 41 | public VelocityServerStats(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { 42 | this.server = server; 43 | this.logger = new LogWrapper() { 44 | @Override 45 | public void info(String msg) { 46 | logger.info(msg); 47 | } 48 | 49 | @Override 50 | public void error(String msg) { 51 | logger.error(msg); 52 | } 53 | }; 54 | 55 | this.dataDirectory = dataDirectory; 56 | } 57 | 58 | @Subscribe 59 | public void onProxyInitialize(ProxyInitializeEvent event) { 60 | this.meta = new VelocityMetadata(server.getPluginManager().getPlugin("serverstats").get().getDescription()); 61 | this.metricsManager = new VelocityMetricsManager(this); 62 | 63 | try { 64 | this.scheduler = new VelocityScheduler(this); 65 | this.serverStats = new ServerStats(this, this.logger); 66 | new VelocityCommandExecutor(this); 67 | 68 | this.start(); 69 | } catch (IOException e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | 74 | @Subscribe 75 | public void onProxyShutdown(ProxyShutdownEvent event) { 76 | this.serverStats.stop(); 77 | } 78 | 79 | @Override 80 | public Path getConfigPath() { 81 | return this.dataDirectory.resolve("serverstats.conf"); 82 | } 83 | 84 | @Override 85 | public ServerStatsMetadata getMetadata() { 86 | return this.meta; 87 | } 88 | 89 | @Override 90 | public Scheduler getScheduler() { 91 | return this.scheduler; 92 | } 93 | 94 | @Override 95 | public MetricsManager getMetricsManager() { 96 | return this.metricsManager; 97 | } 98 | 99 | @Override 100 | public ServerStats getServerStats() { 101 | return this.serverStats; 102 | } 103 | 104 | @Override 105 | public void start() { 106 | this.serverStats.tasks.register(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /velocity/src/main/java/me/diced/serverstats/velocity/command/VelocityCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.velocity.command; 2 | 3 | import com.velocitypowered.api.command.RawCommand; 4 | import com.velocitypowered.api.proxy.ConsoleCommandSource; 5 | import me.diced.serverstats.common.command.CommandExecutor; 6 | import me.diced.serverstats.common.plugin.ServerStats; 7 | import me.diced.serverstats.velocity.VelocityServerStats; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static me.diced.serverstats.common.plugin.Util.tokenize; 13 | 14 | public class VelocityCommandExecutor implements CommandExecutor, RawCommand { 15 | private final VelocityServerStats platform; 16 | 17 | public VelocityCommandExecutor(VelocityServerStats platform) { 18 | this.platform = platform; 19 | this.platform.server.getCommandManager().register("stats", this); 20 | } 21 | 22 | @Override 23 | public ServerStats getPlatform() { 24 | return this.platform.getServerStats(); 25 | } 26 | 27 | @Override 28 | public void execute(Invocation invocation) { 29 | List arguments = tokenize(invocation.arguments()); 30 | 31 | VelocityContext ctx = new VelocityContext(invocation.source()); 32 | 33 | this.executeCommand(arguments, ctx); 34 | } 35 | 36 | @Override 37 | public List suggest(Invocation invocation) { 38 | List res = new ArrayList<>(); 39 | VelocityContext ctx = new VelocityContext(invocation.source()); 40 | VelocityCompletionsManager completions = new VelocityCompletionsManager(res, ctx); 41 | 42 | completions.registerProxy(); 43 | 44 | return res; 45 | } 46 | 47 | @Override 48 | public boolean hasPermission(Invocation invocation) { 49 | return invocation.source() instanceof ConsoleCommandSource; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /velocity/src/main/java/me/diced/serverstats/velocity/command/VelocityCompletionsManager.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.velocity.command; 2 | 3 | import me.diced.serverstats.common.command.CompletionsManager; 4 | import me.diced.serverstats.common.command.Context; 5 | 6 | import java.util.List; 7 | 8 | public class VelocityCompletionsManager extends CompletionsManager> { 9 | public VelocityCompletionsManager(List strings, Context ctx) { 10 | super(strings, ctx); 11 | } 12 | 13 | @Override 14 | public void suggest(String cmd) { 15 | this.suggester.add(cmd); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /velocity/src/main/java/me/diced/serverstats/velocity/command/VelocityContext.java: -------------------------------------------------------------------------------- 1 | package me.diced.serverstats.velocity.command; 2 | 3 | import com.velocitypowered.api.command.CommandSource; 4 | import com.velocitypowered.api.proxy.ConsoleCommandSource; 5 | import me.diced.serverstats.common.command.Context; 6 | import net.kyori.adventure.text.Component; 7 | 8 | public class VelocityContext implements Context { 9 | private final CommandSource sender; 10 | 11 | public VelocityContext(CommandSource sender) { 12 | this.sender = sender; 13 | } 14 | 15 | public void sendMessage(Component message) { 16 | this.sender.sendMessage(message); 17 | } 18 | 19 | public boolean isOp() { 20 | return this.sender instanceof ConsoleCommandSource; 21 | } 22 | } 23 | --------------------------------------------------------------------------------