├── .actrc ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .whitesource ├── LICENSE ├── README.jp.md ├── README.md ├── README.zh-Hans.md ├── bungeecord-prometheus-exporter.iml ├── dashboards └── default_dashboard.json ├── docker ├── bungeecord │ └── config │ │ └── config.yml ├── docker-compose-bungeecord.yml ├── docker-compose-velocity.yml └── velocity │ └── config │ └── velocity.toml ├── images └── dashboard.png ├── pom.xml └── src ├── main ├── assembly │ └── package.xml ├── java │ └── org │ │ └── akadia │ │ └── prometheus │ │ ├── MetricRegistry.java │ │ ├── MetricsServer.java │ │ ├── PrometheusExporter.java │ │ ├── bungeecord │ │ ├── PrometheusBungeeCordExporter.java │ │ ├── listeners │ │ │ ├── LoginEventListener.java │ │ │ ├── PlayerChatEventListener.java │ │ │ ├── PlayerCommandEventListener.java │ │ │ ├── PlayerDisconnectEventListener.java │ │ │ ├── PlayerJoinedNetworkEventListener.java │ │ │ ├── PlayerKickEventListener.java │ │ │ ├── PlayerLeftNetworkEventListener.java │ │ │ └── ProxyPingEventListener.java │ │ └── metrics │ │ │ ├── InstalledNetworkPlugins.java │ │ │ ├── ManagedServers.java │ │ │ ├── OnlinePlayer.java │ │ │ ├── OnlinePlayerLatency.java │ │ │ ├── RedisBungeeOnlinePlayer.java │ │ │ └── RedisBungeeOnlineProxies.java │ │ ├── config │ │ └── ConfigManager.java │ │ ├── interfaces │ │ ├── Configurable.java │ │ ├── CountableMetrics.java │ │ ├── GauageMetric.java │ │ ├── Metric.java │ │ ├── MetricWrapper.java │ │ └── SummaryMetric.java │ │ ├── metrics │ │ ├── JvmGarbageCollectorWrapper.java │ │ ├── JvmMemory.java │ │ └── JvmThreadsWrapper.java │ │ ├── utils │ │ └── Util.java │ │ └── velocity │ │ ├── PrometheusVelocityExporter.java │ │ ├── listeners │ │ ├── LoginEventListener.java │ │ ├── PlayerChatEventListener.java │ │ ├── PlayerCommandEventListener.java │ │ ├── PlayerDisconnectEventListener.java │ │ ├── PlayerKickEventListener.java │ │ └── ProxyPingEventListener.java │ │ └── metrics │ │ ├── InstalledNetworkPlugins.java │ │ ├── ManagedServers.java │ │ ├── OnlinePlayer.java │ │ └── OnlinePlayersLatency.java └── resources │ ├── bungee.yml │ ├── config.json │ └── velocity-plugin.json └── test └── java ├── .gitignore └── org └── akadia └── prometheus └── exporter └── PrometheusExporterTest.java /.actrc: -------------------------------------------------------------------------------- 1 | -P ubuntu-latest=nektos/act-environments-ubuntu:18.04 2 | -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@master 18 | - name: Set up JDK 19 | uses: actions/setup-java@master 20 | with: 21 | java-version: '17' 22 | distribution: 'adopt' 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | - name: Upload a Build Artifact 26 | uses: actions/upload-artifact@master 27 | with: 28 | name: jar artifact 29 | path: /home/runner/work/bungeecord-prometheus-exporter/bungeecord-prometheus-exporter/target/bungeecord-prometheus-exporter-*.jar 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Release 5 | 6 | on: 7 | workflow_dispatch: 8 | release: 9 | types: [ created ] 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@master 18 | - name: Set up JDK 19 | uses: actions/setup-java@master 20 | with: 21 | java-version: '17' 22 | distribution: 'adopt' 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | - name: Release 26 | uses: qcastel/github-actions-maven-release@master 27 | env: 28 | JAVA_HOME: /usr/lib/jvm/java-17-openjdk/ 29 | with: 30 | access-token: ${{ secrets.GITHUB_ACCESS_TOKEN }} 31 | release-branch-name: "main" 32 | maven-options: "-DignoreSnapshots=true" 33 | ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} 34 | maven-repo-server-id: github 35 | maven-repo-server-username: ${{ secrets.MVN_REPO_PRIVATE_REPO_USER }} 36 | maven-repo-server-password: ${{ secrets.MVN_REPO_PRIVATE_REPO_PASSWORD }} 37 | git-release-bot-name: ${{ secrets.BOT_NAME }} 38 | git-release-bot-email: ${{ secrets.BOT_EMAIL }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | .idea 26 | target 27 | 28 | act 29 | 30 | .attach_pid* 31 | 32 | # plugins 33 | plugins -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Weihao 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.jp.md: -------------------------------------------------------------------------------- 1 | [![en](https://img.shields.io/badge/lang-English-blue.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.md) 2 | [![zh-Hans](https://img.shields.io/badge/lang-中文-red.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.zh-Hans.md) 3 | [![jp](https://img.shields.io/badge/lang-日本語-yellow.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.jp.md) 4 | [![Release](https://github.com/weihao/bungeecord-prometheus-exporter/actions/workflows/release.yml/badge.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/actions/workflows/release.yml) 5 | ![GitHub all releases](https://img.shields.io/github/downloads/weihao/bungeecord-prometheus-exporter/total) 6 | 7 | 8 | # BungeeCord Prometheus Exporter 9 | 10 | 翻訳の助けてお願いします -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![en](https://img.shields.io/badge/lang-English-blue.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.md) 2 | [![zh-Hans](https://img.shields.io/badge/lang-中文-red.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.zh-Hans.md) 3 | [![jp](https://img.shields.io/badge/lang-日本語-yellow.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.jp.md) 4 | [![Release](https://github.com/weihao/bungeecord-prometheus-exporter/actions/workflows/release.yml/badge.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/actions/workflows/release.yml) 5 | ![GitHub all releases](https://img.shields.io/github/downloads/weihao/bungeecord-prometheus-exporter/total) 6 | 7 | 8 | # BungeeCord Prometheus Exporter 9 | 10 | A **plugin** that exports network stats for Prometheus. 11 | 12 | > If you don't run a network proxy, you might also be interested 13 | > in [Prometheus Exporter](https://github.com/sladkoff/minecraft-prometheus-exporter) for a `single server` metrics! 14 | 15 | ## Why BungeeCord Prometheus Exporter? 16 | 17 | - monitor your server infrastructure 18 | - track your players, events, and servers 19 | - player pings histogram 20 | - online player list 21 | - server list ping, connects, disconnects, kicks, and chat event counters 22 | - automates the collection, management and viewing of your data 23 | - get alerts for service outages 24 | 25 | ## Runtime Requirement 26 | 27 | - Java 17+ 28 | 29 | ## Compatible Proxy 30 | 31 | - [x] Velocity 32 | - [x] BungeeCord / Waterfall 33 | - [x] RedisBungee 34 | 35 | ## Quick Start 36 | 37 | Drop the bungeecord-prometheus-exporter.jar into your plugins directory and start your proxy server. 38 | 39 | After startup, the Prometheus metrics endpoint should be available at ``localhost:9985/metrics`` (assuming localhost is 40 | the server hostname). 41 | 42 | If running inside the docker, change the host to `0.0.0.0` to allow Prometheus and other services to reach the endpoint. 43 | 44 | The metrics port can be customized in the plugin's config.json (a default config will be created after the first use). 45 | 46 | ## Prometheus config 47 | 48 | Add the following job to the ``scrape_configs`` section of your Prometheus configuration `prometheus.yml`: 49 | 50 | ### Single Proxy 51 | 52 | ```yml 53 | - job_name: 'bungeecord' 54 | scrape_interval: 5s 55 | 56 | static_configs: 57 | - targets: [ 'localhost:9985' ] 58 | labels: 59 | proxy_name: 'proxy' 60 | ``` 61 | 62 | ### Multiple proxies 63 | 64 | You can use labels in your Prometheus scrape configuration to distinguish between multiple proxies: 65 | 66 | ```yml 67 | - job_name: 'bungeecord' 68 | scrape_interval: 5s 69 | 70 | static_configs: 71 | - targets: [ 'localhost:9985' ] 72 | labels: 73 | proxy_name: 'proxy1' 74 | - targets: [ 'localhost:9226' ] 75 | labels: 76 | proxy_name: 'proxy2' 77 | ``` 78 | 79 | ## Import Grafana Dashboard 80 | 81 | 1. Navigate to Grafana -> Dashboards -> Import 82 | 1. Paste in or upload [default dashboard](https://github.com/weihao/bungeecord-prometheus-exporter/tree/main/dashboards) 83 | 1. ![default dashboard](https://raw.githubusercontent.com/weihao/bungeecord-prometheus-exporter/main/images/dashboard.png) 84 | 85 | ## Notes 86 | 87 | RedisBungee is supported 88 | but [disabled by default](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/src/main/resources/config.json) 89 | . 90 | RedisBungee metrics are not used in the dashboard because we are 91 | already collecting metrics from single instances. However, if you still want to integrate with RedisBungee, free feel to 92 | enable it and modify the dashboard. 93 | 94 | ## Links 95 | 96 | This project is indexed at: 97 | 98 | - [exporters](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exporters.md#miscellaneous) 99 | - [ports](https://github.com/prometheus/prometheus/wiki/Default-port-allocations#exporters-starting-at-9100) 100 | -------------------------------------------------------------------------------- /README.zh-Hans.md: -------------------------------------------------------------------------------- 1 | [![en](https://img.shields.io/badge/lang-English-blue.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.md) 2 | [![zh-Hans](https://img.shields.io/badge/lang-中文-red.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.zh-Hans.md) 3 | [![jp](https://img.shields.io/badge/lang-日本語-yellow.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/blob/main/README.jp.md) 4 | [![Release](https://github.com/weihao/bungeecord-prometheus-exporter/actions/workflows/release.yml/badge.svg)](https://github.com/weihao/bungeecord-prometheus-exporter/actions/workflows/release.yml) 5 | ![GitHub all releases](https://img.shields.io/github/downloads/weihao/bungeecord-prometheus-exporter/total) 6 | 7 | 8 | # BungeeCord Prometheus Exporter 9 | 10 | 有没有好心的志愿者维护中文文档呢? -------------------------------------------------------------------------------- /bungeecord-prometheus-exporter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BUNGEECORD 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dashboards/default_dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "annotations": { 13 | "list": [ 14 | { 15 | "builtIn": 1, 16 | "datasource": { 17 | "type": "datasource", 18 | "uid": "grafana" 19 | }, 20 | "enable": true, 21 | "hide": true, 22 | "iconColor": "rgba(0, 211, 255, 1)", 23 | "name": "Annotations & Alerts", 24 | "target": { 25 | "limit": 100, 26 | "matchAny": false, 27 | "tags": [], 28 | "type": "dashboard" 29 | }, 30 | "type": "dashboard" 31 | } 32 | ] 33 | }, 34 | "description": "", 35 | "editable": true, 36 | "fiscalYearStartMonth": 0, 37 | "graphTooltip": 0, 38 | "id": 22, 39 | "links": [], 40 | "liveNow": false, 41 | "panels": [ 42 | { 43 | "datasource": "${DS_PROMETHEUS}", 44 | "fieldConfig": { 45 | "defaults": { 46 | "color": { 47 | "mode": "palette-classic" 48 | }, 49 | "custom": { 50 | "axisCenteredZero": false, 51 | "axisColorMode": "text", 52 | "axisLabel": "", 53 | "axisPlacement": "auto", 54 | "barAlignment": 0, 55 | "drawStyle": "line", 56 | "fillOpacity": 25, 57 | "gradientMode": "none", 58 | "hideFrom": { 59 | "legend": false, 60 | "tooltip": false, 61 | "viz": false 62 | }, 63 | "lineInterpolation": "linear", 64 | "lineWidth": 1, 65 | "pointSize": 5, 66 | "scaleDistribution": { 67 | "type": "linear" 68 | }, 69 | "showPoints": "never", 70 | "spanNulls": false, 71 | "stacking": { 72 | "group": "A", 73 | "mode": "normal" 74 | }, 75 | "thresholdsStyle": { 76 | "mode": "off" 77 | } 78 | }, 79 | "decimals": 0, 80 | "mappings": [], 81 | "noValue": "0", 82 | "thresholds": { 83 | "mode": "absolute", 84 | "steps": [ 85 | { 86 | "color": "green", 87 | "value": null 88 | }, 89 | { 90 | "color": "red", 91 | "value": 80 92 | } 93 | ] 94 | }, 95 | "unit": "short" 96 | }, 97 | "overrides": [] 98 | }, 99 | "gridPos": { 100 | "h": 12, 101 | "w": 12, 102 | "x": 0, 103 | "y": 0 104 | }, 105 | "id": 4, 106 | "options": { 107 | "legend": { 108 | "calcs": [ 109 | "min", 110 | "max", 111 | "mean", 112 | "last" 113 | ], 114 | "displayMode": "table", 115 | "placement": "bottom", 116 | "showLegend": true 117 | }, 118 | "tooltip": { 119 | "mode": "multi", 120 | "sort": "none" 121 | } 122 | }, 123 | "pluginVersion": "8.2.1", 124 | "targets": [ 125 | { 126 | "datasource": "${DS_PROMETHEUS}", 127 | "editorMode": "code", 128 | "exemplar": false, 129 | "expr": "sum by (server) (bungeecord_online_player{proxy_name=~\"$proxy\"})", 130 | "hide": false, 131 | "instant": false, 132 | "interval": "", 133 | "legendFormat": "{{server}}", 134 | "range": true, 135 | "refId": "A" 136 | } 137 | ], 138 | "title": "Online Players", 139 | "type": "timeseries" 140 | }, 141 | { 142 | "datasource": "${DS_PROMETHEUS}", 143 | "description": "", 144 | "fieldConfig": { 145 | "defaults": { 146 | "color": { 147 | "mode": "palette-classic" 148 | }, 149 | "custom": { 150 | "axisCenteredZero": false, 151 | "axisColorMode": "text", 152 | "axisLabel": "", 153 | "axisPlacement": "auto", 154 | "barAlignment": 0, 155 | "drawStyle": "line", 156 | "fillOpacity": 25, 157 | "gradientMode": "none", 158 | "hideFrom": { 159 | "legend": false, 160 | "tooltip": false, 161 | "viz": false 162 | }, 163 | "lineInterpolation": "linear", 164 | "lineStyle": { 165 | "fill": "solid" 166 | }, 167 | "lineWidth": 1, 168 | "pointSize": 5, 169 | "scaleDistribution": { 170 | "type": "linear" 171 | }, 172 | "showPoints": "never", 173 | "spanNulls": false, 174 | "stacking": { 175 | "group": "A", 176 | "mode": "none" 177 | }, 178 | "thresholdsStyle": { 179 | "mode": "off" 180 | } 181 | }, 182 | "decimals": 0, 183 | "mappings": [], 184 | "thresholds": { 185 | "mode": "absolute", 186 | "steps": [ 187 | { 188 | "color": "green", 189 | "value": null 190 | }, 191 | { 192 | "color": "red", 193 | "value": 80 194 | } 195 | ] 196 | }, 197 | "unit": "short" 198 | }, 199 | "overrides": [] 200 | }, 201 | "gridPos": { 202 | "h": 12, 203 | "w": 12, 204 | "x": 12, 205 | "y": 0 206 | }, 207 | "id": 2, 208 | "options": { 209 | "legend": { 210 | "calcs": [ 211 | "mean", 212 | "max", 213 | "last" 214 | ], 215 | "displayMode": "table", 216 | "placement": "bottom", 217 | "showLegend": true 218 | }, 219 | "tooltip": { 220 | "mode": "multi", 221 | "sort": "desc" 222 | } 223 | }, 224 | "pluginVersion": "8.2.1", 225 | "targets": [ 226 | { 227 | "datasource": "${DS_PROMETHEUS}", 228 | "editorMode": "code", 229 | "expr": "increase(bungeecord_player_chats_total{proxy_name=~\"$proxy\"}[1m])", 230 | "hide": false, 231 | "legendFormat": "chats", 232 | "range": true, 233 | "refId": "A" 234 | }, 235 | { 236 | "datasource": "${DS_PROMETHEUS}", 237 | "editorMode": "code", 238 | "exemplar": false, 239 | "expr": "increase(bungeecord_server_list_pings_total{proxy_name=~\"$proxy\"}[1m])", 240 | "hide": false, 241 | "interval": "", 242 | "legendFormat": "server list pings", 243 | "range": true, 244 | "refId": "B" 245 | }, 246 | { 247 | "datasource": "${DS_PROMETHEUS}", 248 | "editorMode": "code", 249 | "exemplar": false, 250 | "expr": "increase(bungeecord_player_connects_total{proxy_name=~\"$proxy\"}[1m])", 251 | "hide": false, 252 | "interval": "", 253 | "legendFormat": "connects", 254 | "range": true, 255 | "refId": "C" 256 | }, 257 | { 258 | "datasource": "${DS_PROMETHEUS}", 259 | "editorMode": "code", 260 | "expr": "increase(bungeecord_player_kicks_total{proxy_name=~\"$proxy\"}[1m])", 261 | "hide": false, 262 | "legendFormat": "kicks", 263 | "range": true, 264 | "refId": "D" 265 | }, 266 | { 267 | "datasource": "${DS_PROMETHEUS}", 268 | "editorMode": "code", 269 | "exemplar": false, 270 | "expr": "increase(bungeecord_player_disconnects_total{proxy_name=~\"$proxy\"}[1m])", 271 | "hide": false, 272 | "interval": "", 273 | "legendFormat": "disconnects", 274 | "range": true, 275 | "refId": "F" 276 | } 277 | ], 278 | "title": "Events per Minute", 279 | "type": "timeseries" 280 | }, 281 | { 282 | "datasource": "${DS_PROMETHEUS}", 283 | "fieldConfig": { 284 | "defaults": { 285 | "color": { 286 | "mode": "palette-classic" 287 | }, 288 | "custom": { 289 | "axisCenteredZero": false, 290 | "axisColorMode": "text", 291 | "axisLabel": "", 292 | "axisPlacement": "auto", 293 | "barAlignment": 0, 294 | "drawStyle": "line", 295 | "fillOpacity": 10, 296 | "gradientMode": "hue", 297 | "hideFrom": { 298 | "legend": false, 299 | "tooltip": false, 300 | "viz": false 301 | }, 302 | "lineInterpolation": "smooth", 303 | "lineStyle": { 304 | "fill": "solid" 305 | }, 306 | "lineWidth": 1, 307 | "pointSize": 5, 308 | "scaleDistribution": { 309 | "type": "linear" 310 | }, 311 | "showPoints": "never", 312 | "spanNulls": false, 313 | "stacking": { 314 | "group": "A", 315 | "mode": "none" 316 | }, 317 | "thresholdsStyle": { 318 | "mode": "off" 319 | } 320 | }, 321 | "links": [], 322 | "mappings": [], 323 | "min": 0, 324 | "thresholds": { 325 | "mode": "absolute", 326 | "steps": [ 327 | { 328 | "color": "blue", 329 | "value": null 330 | } 331 | ] 332 | }, 333 | "unit": "bytes" 334 | }, 335 | "overrides": [] 336 | }, 337 | "gridPos": { 338 | "h": 10, 339 | "w": 6, 340 | "x": 0, 341 | "y": 12 342 | }, 343 | "id": 5, 344 | "links": [], 345 | "options": { 346 | "legend": { 347 | "calcs": [ 348 | "min", 349 | "max", 350 | "mean", 351 | "last" 352 | ], 353 | "displayMode": "table", 354 | "placement": "bottom", 355 | "showLegend": true 356 | }, 357 | "tooltip": { 358 | "mode": "single", 359 | "sort": "none" 360 | } 361 | }, 362 | "pluginVersion": "8.0.6", 363 | "targets": [ 364 | { 365 | "datasource": "${DS_PROMETHEUS}", 366 | "exemplar": false, 367 | "expr": "sum by (type) (bungeecord_jvm_memory{type='used',proxy_name=~\"$proxy\"})", 368 | "hide": false, 369 | "interval": "", 370 | "legendFormat": "{{server_name}} used", 371 | "refId": "C" 372 | }, 373 | { 374 | "datasource": "${DS_PROMETHEUS}", 375 | "exemplar": false, 376 | "expr": "sum by (type) (bungeecord_jvm_memory{type='free',proxy_name=~\"$proxy\"})", 377 | "hide": false, 378 | "interval": "", 379 | "legendFormat": "{{server_name}} free", 380 | "refId": "A" 381 | } 382 | ], 383 | "title": "JVM Memory", 384 | "type": "timeseries" 385 | }, 386 | { 387 | "aliasColors": {}, 388 | "bars": false, 389 | "dashLength": 10, 390 | "dashes": false, 391 | "datasource": "${DS_PROMETHEUS}", 392 | "fill": 1, 393 | "fillGradient": 1, 394 | "gridPos": { 395 | "h": 10, 396 | "w": 6, 397 | "x": 6, 398 | "y": 12 399 | }, 400 | "hiddenSeries": false, 401 | "id": 11, 402 | "legend": { 403 | "avg": false, 404 | "current": false, 405 | "max": false, 406 | "min": false, 407 | "show": true, 408 | "total": false, 409 | "values": false 410 | }, 411 | "lines": true, 412 | "linewidth": 1, 413 | "nullPointMode": "null", 414 | "options": { 415 | "alertThreshold": true 416 | }, 417 | "percentage": false, 418 | "pluginVersion": "9.4.3", 419 | "pointradius": 2, 420 | "points": false, 421 | "renderer": "flot", 422 | "seriesOverrides": [], 423 | "spaceLength": 10, 424 | "stack": false, 425 | "steppedLine": false, 426 | "targets": [ 427 | { 428 | "datasource": "${DS_PROMETHEUS}", 429 | "exemplar": false, 430 | "expr": "sum(bungeecord_jvm_threads_current{proxy_name=~\"$proxy\"})", 431 | "interval": "", 432 | "legendFormat": "TOTAL", 433 | "refId": "A" 434 | }, 435 | { 436 | "datasource": "${DS_PROMETHEUS}", 437 | "exemplar": false, 438 | "expr": "sum by (state) (bungeecord_jvm_threads_state{proxy_name=~\"$proxy\"})", 439 | "hide": false, 440 | "interval": "", 441 | "legendFormat": "{{state}}", 442 | "refId": "B" 443 | } 444 | ], 445 | "thresholds": [], 446 | "timeRegions": [], 447 | "title": "Threads", 448 | "tooltip": { 449 | "shared": true, 450 | "sort": 0, 451 | "value_type": "individual" 452 | }, 453 | "type": "graph", 454 | "xaxis": { 455 | "mode": "time", 456 | "show": true, 457 | "values": [] 458 | }, 459 | "yaxes": [ 460 | { 461 | "format": "short", 462 | "logBase": 1, 463 | "show": true 464 | }, 465 | { 466 | "format": "short", 467 | "logBase": 1, 468 | "show": true 469 | } 470 | ], 471 | "yaxis": { 472 | "align": false 473 | } 474 | }, 475 | { 476 | "datasource": "${DS_PROMETHEUS}", 477 | "description": "", 478 | "fieldConfig": { 479 | "defaults": { 480 | "color": { 481 | "mode": "thresholds" 482 | }, 483 | "displayName": "", 484 | "links": [], 485 | "mappings": [], 486 | "max": 4000000000, 487 | "min": 0, 488 | "thresholds": { 489 | "mode": "absolute", 490 | "steps": [ 491 | { 492 | "color": "blue", 493 | "value": null 494 | }, 495 | { 496 | "color": "red", 497 | "value": 3000000000 498 | } 499 | ] 500 | }, 501 | "unit": "bytes" 502 | }, 503 | "overrides": [] 504 | }, 505 | "gridPos": { 506 | "h": 3, 507 | "w": 3, 508 | "x": 12, 509 | "y": 12 510 | }, 511 | "id": 9, 512 | "options": { 513 | "orientation": "auto", 514 | "reduceOptions": { 515 | "calcs": [ 516 | "last" 517 | ], 518 | "fields": "", 519 | "values": false 520 | }, 521 | "showThresholdLabels": false, 522 | "showThresholdMarkers": true, 523 | "text": {} 524 | }, 525 | "pluginVersion": "9.4.3", 526 | "repeat": "proxies", 527 | "repeatDirection": "v", 528 | "targets": [ 529 | { 530 | "datasource": "${DS_PROMETHEUS}", 531 | "exemplar": false, 532 | "expr": "sum(bungeecord_jvm_memory{type='allocated', proxy_name=~\"$proxy\"}) - scalar(sum(bungeecord_jvm_memory{type='free', proxy_name=~\"$proxy\"}))", 533 | "instant": false, 534 | "interval": "", 535 | "intervalFactor": 1, 536 | "legendFormat": "", 537 | "refId": "A" 538 | } 539 | ], 540 | "title": "$proxy RAM", 541 | "type": "gauge" 542 | }, 543 | { 544 | "datasource": "${DS_PROMETHEUS}", 545 | "fieldConfig": { 546 | "defaults": { 547 | "color": { 548 | "mode": "thresholds" 549 | }, 550 | "mappings": [], 551 | "thresholds": { 552 | "mode": "absolute", 553 | "steps": [ 554 | { 555 | "color": "blue", 556 | "value": null 557 | } 558 | ] 559 | } 560 | }, 561 | "overrides": [] 562 | }, 563 | "gridPos": { 564 | "h": 3, 565 | "w": 2, 566 | "x": 15, 567 | "y": 12 568 | }, 569 | "id": 18, 570 | "options": { 571 | "colorMode": "value", 572 | "graphMode": "none", 573 | "justifyMode": "center", 574 | "orientation": "auto", 575 | "reduceOptions": { 576 | "calcs": [ 577 | "lastNotNull" 578 | ], 579 | "fields": "", 580 | "values": false 581 | }, 582 | "text": {}, 583 | "textMode": "auto" 584 | }, 585 | "pluginVersion": "9.4.3", 586 | "targets": [ 587 | { 588 | "datasource": "${DS_PROMETHEUS}", 589 | "exemplar": false, 590 | "expr": "count(count(bungeecord_jvm_memory{proxy_name=~\".*\"}) by (proxy_name))", 591 | "interval": "", 592 | "legendFormat": "", 593 | "refId": "A" 594 | } 595 | ], 596 | "title": "Proxies", 597 | "type": "stat" 598 | }, 599 | { 600 | "datasource": "${DS_PROMETHEUS}", 601 | "fieldConfig": { 602 | "defaults": { 603 | "color": { 604 | "mode": "thresholds" 605 | }, 606 | "mappings": [], 607 | "min": 0, 608 | "thresholds": { 609 | "mode": "absolute", 610 | "steps": [ 611 | { 612 | "color": "blue", 613 | "value": null 614 | } 615 | ] 616 | }, 617 | "unit": "none" 618 | }, 619 | "overrides": [] 620 | }, 621 | "gridPos": { 622 | "h": 3, 623 | "w": 2, 624 | "x": 17, 625 | "y": 12 626 | }, 627 | "id": 20, 628 | "options": { 629 | "colorMode": "value", 630 | "graphMode": "none", 631 | "justifyMode": "center", 632 | "orientation": "auto", 633 | "reduceOptions": { 634 | "calcs": [ 635 | "lastNotNull" 636 | ], 637 | "fields": "", 638 | "values": false 639 | }, 640 | "text": {}, 641 | "textMode": "auto" 642 | }, 643 | "pluginVersion": "9.4.3", 644 | "targets": [ 645 | { 646 | "datasource": "${DS_PROMETHEUS}", 647 | "exemplar": false, 648 | "expr": "bungeecord_managed_servers{proxy_name=~\"$proxy\"}", 649 | "interval": "", 650 | "legendFormat": "{{proxy_name}}", 651 | "refId": "A" 652 | } 653 | ], 654 | "title": "Servers", 655 | "type": "stat" 656 | }, 657 | { 658 | "datasource": "${DS_PROMETHEUS}", 659 | "fieldConfig": { 660 | "defaults": { 661 | "color": { 662 | "mode": "thresholds" 663 | }, 664 | "mappings": [], 665 | "min": 0, 666 | "thresholds": { 667 | "mode": "absolute", 668 | "steps": [ 669 | { 670 | "color": "blue", 671 | "value": null 672 | } 673 | ] 674 | }, 675 | "unit": "none" 676 | }, 677 | "overrides": [] 678 | }, 679 | "gridPos": { 680 | "h": 3, 681 | "w": 2, 682 | "x": 19, 683 | "y": 12 684 | }, 685 | "id": 23, 686 | "options": { 687 | "colorMode": "value", 688 | "graphMode": "none", 689 | "justifyMode": "center", 690 | "orientation": "auto", 691 | "reduceOptions": { 692 | "calcs": [ 693 | "lastNotNull" 694 | ], 695 | "fields": "", 696 | "values": false 697 | }, 698 | "text": {}, 699 | "textMode": "auto" 700 | }, 701 | "pluginVersion": "9.4.3", 702 | "targets": [ 703 | { 704 | "datasource": "${DS_PROMETHEUS}", 705 | "editorMode": "code", 706 | "exemplar": false, 707 | "expr": "bungeecord_installed_network_plugins{proxy_name=~\"$proxy\"}", 708 | "interval": "", 709 | "legendFormat": "{{proxy_name}}", 710 | "range": true, 711 | "refId": "A" 712 | } 713 | ], 714 | "title": "Plugins", 715 | "type": "stat" 716 | }, 717 | { 718 | "datasource": "${DS_PROMETHEUS}", 719 | "fieldConfig": { 720 | "defaults": { 721 | "color": { 722 | "mode": "thresholds" 723 | }, 724 | "mappings": [], 725 | "min": 0, 726 | "thresholds": { 727 | "mode": "absolute", 728 | "steps": [ 729 | { 730 | "color": "blue", 731 | "value": null 732 | } 733 | ] 734 | }, 735 | "unit": "none" 736 | }, 737 | "overrides": [] 738 | }, 739 | "gridPos": { 740 | "h": 3, 741 | "w": 3, 742 | "x": 21, 743 | "y": 12 744 | }, 745 | "id": 13, 746 | "options": { 747 | "colorMode": "value", 748 | "graphMode": "area", 749 | "justifyMode": "center", 750 | "orientation": "auto", 751 | "reduceOptions": { 752 | "calcs": [ 753 | "lastNotNull" 754 | ], 755 | "fields": "", 756 | "values": false 757 | }, 758 | "text": {}, 759 | "textMode": "auto" 760 | }, 761 | "pluginVersion": "9.4.3", 762 | "targets": [ 763 | { 764 | "datasource": "${DS_PROMETHEUS}", 765 | "editorMode": "code", 766 | "exemplar": false, 767 | "expr": "sum(bungeecord_online_player{proxy_name=~\"$proxy\"})", 768 | "instant": false, 769 | "interval": "", 770 | "legendFormat": "Online Player", 771 | "range": true, 772 | "refId": "A" 773 | } 774 | ], 775 | "title": "Player Count", 776 | "transformations": [], 777 | "type": "stat" 778 | }, 779 | { 780 | "datasource": "${DS_PROMETHEUS}", 781 | "fieldConfig": { 782 | "defaults": { 783 | "color": { 784 | "mode": "thresholds" 785 | }, 786 | "custom": { 787 | "fillOpacity": 100, 788 | "gradientMode": "none", 789 | "hideFrom": { 790 | "legend": false, 791 | "tooltip": false, 792 | "viz": false 793 | }, 794 | "lineWidth": 1 795 | }, 796 | "mappings": [], 797 | "max": 1000, 798 | "min": 0, 799 | "thresholds": { 800 | "mode": "absolute", 801 | "steps": [ 802 | { 803 | "color": "green", 804 | "value": null 805 | } 806 | ] 807 | }, 808 | "unit": "ms" 809 | }, 810 | "overrides": [] 811 | }, 812 | "gridPos": { 813 | "h": 7, 814 | "w": 9, 815 | "x": 12, 816 | "y": 15 817 | }, 818 | "id": 22, 819 | "options": { 820 | "bucketOffset": 0, 821 | "bucketSize": 20, 822 | "combine": false, 823 | "legend": { 824 | "calcs": [], 825 | "displayMode": "list", 826 | "placement": "bottom", 827 | "showLegend": false 828 | } 829 | }, 830 | "pluginVersion": "8.2.1", 831 | "targets": [ 832 | { 833 | "datasource": "${DS_PROMETHEUS}", 834 | "exemplar": false, 835 | "expr": "clamp_max(avg(bungeecord_online_player_latency > 0),1000)", 836 | "interval": "", 837 | "legendFormat": "average player ping", 838 | "refId": "A" 839 | } 840 | ], 841 | "title": "Ping", 842 | "type": "histogram" 843 | }, 844 | { 845 | "datasource": "${DS_PROMETHEUS}", 846 | "fieldConfig": { 847 | "defaults": { 848 | "color": { 849 | "mode": "thresholds" 850 | }, 851 | "custom": { 852 | "align": "auto", 853 | "cellOptions": { 854 | "type": "auto" 855 | }, 856 | "inspect": false 857 | }, 858 | "mappings": [], 859 | "noValue": "No One 💤", 860 | "thresholds": { 861 | "mode": "absolute", 862 | "steps": [ 863 | { 864 | "color": "green", 865 | "value": null 866 | }, 867 | { 868 | "color": "red", 869 | "value": 80 870 | } 871 | ] 872 | } 873 | }, 874 | "overrides": [] 875 | }, 876 | "gridPos": { 877 | "h": 7, 878 | "w": 3, 879 | "x": 21, 880 | "y": 15 881 | }, 882 | "id": 25, 883 | "options": { 884 | "footer": { 885 | "countRows": false, 886 | "fields": "", 887 | "reducer": [ 888 | "sum" 889 | ], 890 | "show": false 891 | }, 892 | "showHeader": true, 893 | "sortBy": [] 894 | }, 895 | "pluginVersion": "9.4.3", 896 | "targets": [ 897 | { 898 | "datasource": "${DS_PROMETHEUS}", 899 | "editorMode": "code", 900 | "exemplar": false, 901 | "expr": "bungeecord_online_player > 0", 902 | "format": "table", 903 | "instant": true, 904 | "interval": "", 905 | "legendFormat": "{{label_name}}", 906 | "range": false, 907 | "refId": "A" 908 | } 909 | ], 910 | "title": "Online Player List", 911 | "transformations": [ 912 | { 913 | "id": "organize", 914 | "options": { 915 | "excludeByName": { 916 | "Time": true, 917 | "Value": true, 918 | "__name__": true, 919 | "instance": true, 920 | "job": true, 921 | "name": false, 922 | "proxy_name": true, 923 | "quantile": true, 924 | "server": false 925 | }, 926 | "indexByName": {}, 927 | "renameByName": {} 928 | } 929 | } 930 | ], 931 | "type": "table" 932 | } 933 | ], 934 | "refresh": "5s", 935 | "revision": 1, 936 | "schemaVersion": 38, 937 | "style": "dark", 938 | "tags": [ 939 | "bungeecord" 940 | ], 941 | "templating": { 942 | "list": [ 943 | { 944 | "current": { 945 | "selected": false, 946 | "text": "All", 947 | "value": "$__all" 948 | }, 949 | "datasource": "${DS_PROMETHEUS}", 950 | "definition": "label_values(bungeecord_managed_servers, proxy_name)", 951 | "hide": 0, 952 | "includeAll": true, 953 | "multi": false, 954 | "name": "proxy", 955 | "options": [], 956 | "query": { 957 | "query": "label_values(bungeecord_managed_servers, proxy_name)", 958 | "refId": "StandardVariableQuery" 959 | }, 960 | "refresh": 1, 961 | "regex": "", 962 | "skipUrlSync": false, 963 | "sort": 0, 964 | "tagValuesQuery": "", 965 | "tagsQuery": "", 966 | "type": "query", 967 | "useTags": false 968 | } 969 | ] 970 | }, 971 | "time": { 972 | "from": "now-24h", 973 | "to": "now" 974 | }, 975 | "timepicker": {}, 976 | "timezone": "", 977 | "title": "Minecraft Network", 978 | "uid": "AltFNh7nz", 979 | "version": 8, 980 | "weekStart": "" 981 | } -------------------------------------------------------------------------------- /docker/bungeecord/config/config.yml: -------------------------------------------------------------------------------- 1 | server_connect_timeout: 5000 2 | enforce_secure_profile: false 3 | remote_ping_cache: -1 4 | forge_support: true 5 | player_limit: -1 6 | permissions: 7 | default: 8 | - bungeecord.command.server 9 | - bungeecord.command.list 10 | admin: 11 | - bungeecord.command.alert 12 | - bungeecord.command.end 13 | - bungeecord.command.ip 14 | - bungeecord.command.reload 15 | - bungeecord.command.kick 16 | timeout: 30000 17 | log_commands: false 18 | network_compression_threshold: 256 19 | online_mode: false 20 | disabled_commands: 21 | - disabledcommandhere 22 | servers: 23 | lobby: 24 | motd: '&1Just another Waterfall - Forced Host' 25 | address: lobby:25565 26 | restricted: false 27 | smp: 28 | motd: '&1Just another Waterfall - Forced Host' 29 | address: smp:25565 30 | restricted: false 31 | listeners: 32 | - query_port: 25565 33 | motd: '&1Another Bungee server to test Prometheus' 34 | tab_list: GLOBAL_PING 35 | query_enabled: false 36 | proxy_protocol: false 37 | forced_hosts: 38 | pvp.md-5.net: pvp 39 | ping_passthrough: false 40 | priorities: 41 | - lobby 42 | - smp 43 | bind_local_address: true 44 | host: 0.0.0.0:25565 45 | max_players: 1 46 | tab_size: 60 47 | force_default_server: false 48 | ip_forward: false 49 | remote_ping_timeout: 5000 50 | reject_transfers: false 51 | prevent_proxy_connections: false 52 | groups: 53 | md_5: 54 | - admin 55 | connection_throttle: 4000 56 | stats: 6ef14939-5f39-49f5-9a56-e652a1bf4680 57 | connection_throttle_limit: 3 58 | log_pings: true 59 | -------------------------------------------------------------------------------- /docker/docker-compose-bungeecord.yml: -------------------------------------------------------------------------------- 1 | services: 2 | bungeecord: 3 | image: itzg/mc-proxy 4 | ports: 5 | - "25565:25565" 6 | - "9985:9985" 7 | volumes: 8 | - "./bungeecord/plugins:/server/plugins" # <- read/write 9 | - "./bungeecord/config:/config:ro" 10 | depends_on: 11 | - smp 12 | - lobby 13 | environment: 14 | TYPE: "WATERFALL" 15 | VELOCITY_VERSION: "latest" 16 | MEMORY: "256m" 17 | 18 | smp: 19 | image: itzg/minecraft-server 20 | environment: 21 | EULA: "TRUE" 22 | ONLINE_MODE: "FALSE" 23 | TYPE: "PAPER" 24 | MAX_MEMORY: "1G" 25 | COPY_CONFIG_DEST: "/data" 26 | USE_AIKAR_FLAGS: "FALSE" 27 | 28 | lobby: 29 | image: itzg/minecraft-server 30 | environment: 31 | EULA: "TRUE" 32 | ONLINE_MODE: "FALSE" 33 | TYPE: "PAPER" 34 | MAX_MEMORY: "1G" 35 | COPY_CONFIG_DEST: "/data" 36 | USE_AIKAR_FLAGS: "FALSE" 37 | -------------------------------------------------------------------------------- /docker/docker-compose-velocity.yml: -------------------------------------------------------------------------------- 1 | services: 2 | velocity: 3 | image: itzg/mc-proxy 4 | ports: 5 | - "25565:25565" 6 | - "9985:9985" 7 | volumes: 8 | - "./velocity/plugins:/server/plugins" # <- read/write 9 | - "./velocity/config:/config:ro" 10 | depends_on: 11 | - smp 12 | - lobby 13 | environment: 14 | TYPE: "VELOCITY" 15 | VELOCITY_VERSION: "latest" 16 | MEMORY: "256m" 17 | 18 | 19 | smp: 20 | image: itzg/minecraft-server 21 | environment: 22 | EULA: "TRUE" 23 | ONLINE_MODE: "FALSE" 24 | TYPE: "PAPER" 25 | MAX_MEMORY: "1G" 26 | COPY_CONFIG_DEST: "/data" 27 | USE_AIKAR_FLAGS: "FALSE" 28 | 29 | lobby: 30 | image: itzg/minecraft-server 31 | environment: 32 | EULA: "TRUE" 33 | ONLINE_MODE: "FALSE" 34 | TYPE: "PAPER" 35 | MAX_MEMORY: "1G" 36 | COPY_CONFIG_DEST: "/data" 37 | USE_AIKAR_FLAGS: "FALSE" 38 | -------------------------------------------------------------------------------- /docker/velocity/config/velocity.toml: -------------------------------------------------------------------------------- 1 | # Config version. Do not change this 2 | config-version = "2.7" 3 | 4 | # What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565. 5 | bind = "0.0.0.0:25565" 6 | 7 | # What should be the MOTD? This gets displayed when the player adds your server to 8 | # their server list. Only MiniMessage format is accepted. 9 | motd = "<#09add3>A Velocity Server to test Prometheus Exporter" 10 | 11 | # What should we display for the maximum number of players? (Velocity does not support a cap 12 | # on the number of players online.) 13 | show-max-players = 500 14 | 15 | # Should we authenticate players with Mojang? By default, this is on. 16 | online-mode = false 17 | 18 | # Should the proxy enforce the new public key security standard? By default, this is on. 19 | force-key-authentication = true 20 | 21 | # If client's ISP/AS sent from this proxy is different from the one from Mojang's 22 | # authentication server, the player is kicked. This disallows some VPN and proxy 23 | # connections but is a weak form of protection. 24 | prevent-client-proxy-connections = false 25 | 26 | # Should we forward IP addresses and other data to backend servers? 27 | # Available options: 28 | # - "none": No forwarding will be done. All players will appear to be connecting 29 | # from the proxy and will have offline-mode UUIDs. 30 | # - "legacy": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this 31 | # if you run servers using Minecraft 1.12 or lower. 32 | # - "bungeeguard": Forward player IPs and UUIDs in a format supported by the BungeeGuard 33 | # plugin. Use this if you run servers using Minecraft 1.12 or lower, and are 34 | # unable to implement network level firewalling (on a shared host). 35 | # - "modern": Forward player IPs and UUIDs as part of the login process using 36 | # Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher. 37 | player-info-forwarding-mode = "NONE" 38 | 39 | # If you are using modern or BungeeGuard IP forwarding, configure a file that contains a unique secret here. 40 | # The file is expected to be UTF-8 encoded and not empty. 41 | forwarding-secret-file = "forwarding.secret" 42 | 43 | # Announce whether or not your server supports Forge. If you run a modded server, we 44 | # suggest turning this on. 45 | # 46 | # If your network runs one modpack consistently, consider using ping-passthrough = "mods" 47 | # instead for a nicer display in the server list. 48 | announce-forge = false 49 | 50 | # If enabled (default is false) and the proxy is in online mode, Velocity will kick 51 | # any existing player who is online if a duplicate connection attempt is made. 52 | kick-existing-players = false 53 | 54 | # Should Velocity pass server list ping requests to a backend server? 55 | # Available options: 56 | # - "disabled": No pass-through will be done. The velocity.toml and server-icon.png 57 | # will determine the initial server list ping response. 58 | # - "mods": Passes only the mod list from your backend server into the response. 59 | # The first server in your try list (or forced host) with a mod list will be 60 | # used. If no backend servers can be contacted, Velocity won't display any 61 | # mod information. 62 | # - "description": Uses the description and mod list from the backend server. The first 63 | # server in the try (or forced host) list that responds is used for the 64 | # description and mod list. 65 | # - "all": Uses the backend server's response as the proxy response. The Velocity 66 | # configuration is used if no servers could be contacted. 67 | ping-passthrough = "DISABLED" 68 | 69 | # If not enabled (default is true) player IP addresses will be replaced by in logs 70 | enable-player-address-logging = true 71 | 72 | [servers] 73 | # Configure your servers here. Each key represents the server's name, and the value 74 | # represents the IP address of the server to connect to. 75 | lobby = "lobby:25565" 76 | smp = "smp:25565" 77 | 78 | # In what order we should try servers when a player logs in or is kicked from a server. 79 | try = [ 80 | "lobby", 81 | "smp" 82 | ] 83 | 84 | [forced-hosts] 85 | # Configure your forced hosts here. 86 | 87 | [advanced] 88 | # How large a Minecraft packet has to be before we compress it. Setting this to zero will 89 | # compress all packets, and setting it to -1 will disable compression entirely. 90 | compression-threshold = 256 91 | 92 | # How much compression should be done (from 0-9). The default is -1, which uses the 93 | # default level of 6. 94 | compression-level = -1 95 | 96 | # How fast (in milliseconds) are clients allowed to connect after the last connection? By 97 | # default, this is three seconds. Disable this by setting this to 0. 98 | login-ratelimit = 3000 99 | 100 | # Specify a custom timeout for connection timeouts here. The default is five seconds. 101 | connection-timeout = 5000 102 | 103 | # Specify a read timeout for connections here. The default is 30 seconds. 104 | read-timeout = 30000 105 | 106 | # Enables compatibility with HAProxy's PROXY protocol. If you don't know what this is for, then 107 | # don't enable it. 108 | haproxy-protocol = false 109 | 110 | # Enables TCP fast open support on the proxy. Requires the proxy to run on Linux. 111 | tcp-fast-open = false 112 | 113 | # Enables BungeeCord plugin messaging channel support on Velocity. 114 | bungee-plugin-message-channel = true 115 | 116 | # Shows ping requests to the proxy from clients. 117 | show-ping-requests = false 118 | 119 | # By default, Velocity will attempt to gracefully handle situations where the user unexpectedly 120 | # loses connection to the server without an explicit disconnect message by attempting to fall the 121 | # user back, except in the case of read timeouts. BungeeCord will disconnect the user instead. You 122 | # can disable this setting to use the BungeeCord behavior. 123 | failover-on-unexpected-server-disconnect = true 124 | 125 | # Declares the proxy commands to 1.13+ clients. 126 | announce-proxy-commands = true 127 | 128 | # Enables the logging of commands 129 | log-command-executions = false 130 | 131 | # Enables logging of player connections when connecting to the proxy, switching servers 132 | # and disconnecting from the proxy. 133 | log-player-connections = true 134 | 135 | # Allows players transferred from other hosts via the 136 | # Transfer packet (Minecraft 1.20.5) to be received. 137 | accepts-transfers = false 138 | 139 | [query] 140 | # Whether to enable responding to GameSpy 4 query responses or not. 141 | enabled = false 142 | 143 | # If query is enabled, on what port should the query protocol listen on? 144 | port = 25565 145 | 146 | # This is the map name that is reported to the query services. 147 | map = "Velocity" 148 | 149 | # Whether plugins should be shown in query response by default or not 150 | show-plugins = false 151 | -------------------------------------------------------------------------------- /images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weihao/bungeecord-prometheus-exporter/993acbc55dd597cefb23b621f83906440904eec0/images/dashboard.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.akadia 6 | bungeecord-prometheus-exporter 7 | BungeeCordPrometheusExporter 8 | 3.2.8-SNAPSHOT 9 | jar 10 | https://akadia.org 11 | 12 | BungeeCord Prometheus Exporter 13 | 14 | 15 | https://github.com/weihao/bungeecord-prometheus-exporter 16 | scm:git:git://github.com/weihao/bungeecord-prometheus-exporter.git 17 | scm:git:git@github.com:weihao/bungeecord-prometheus-exporter.git 18 | HEAD 19 | 20 | 21 | 22 | 23 | github 24 | GitHub Packages 25 | https://maven.pkg.github.com/weihao/bungeecord-prometheus-exporter 26 | 27 | 28 | 29 | 30 | 31 | 11.0.24 32 | 0.16.0 33 | 2.9.0 34 | 5.15.2 35 | 5.10.3 36 | 3.5.2 37 | 3.14.0 38 | 39 | 40 | 41 | 42 | bungeecord-repo 43 | https://oss.sonatype.org/content/repositories/snapshots 44 | 45 | 46 | jitpack.io 47 | https://jitpack.io 48 | 49 | 50 | velocity 51 | https://nexus.velocitypowered.com/repository/maven-public/ 52 | 53 | 54 | 55 | 56 | 57 | com.google.code.gson 58 | gson 59 | 2.11.0 60 | 61 | 62 | com.velocitypowered 63 | velocity-api 64 | 3.3.0-SNAPSHOT 65 | provided 66 | 67 | 68 | net.md-5 69 | bungeecord-api 70 | 1.20-R0.2 71 | jar 72 | provided 73 | 74 | 75 | net.md-5 76 | bungeecord-api 77 | 1.20-R0.2 78 | javadoc 79 | provided 80 | 81 | 82 | org.eclipse.jetty 83 | jetty-server 84 | ${jetty.version} 85 | 86 | 87 | io.prometheus 88 | simpleclient_common 89 | ${prometheus-client.version} 90 | 91 | 92 | io.prometheus 93 | simpleclient_hotspot 94 | ${prometheus-client.version} 95 | 96 | 97 | com.jayway.jsonpath 98 | json-path 99 | ${json-path.version} 100 | 101 | 102 | 103 | io.prometheus 104 | simpleclient 105 | 0.16.0 106 | 107 | 108 | 109 | io.prometheus 110 | simpleclient_hotspot 111 | 0.16.0 112 | 113 | 114 | 115 | io.prometheus 116 | simpleclient_httpserver 117 | 0.16.0 118 | 119 | 120 | 121 | io.prometheus 122 | simpleclient_pushgateway 123 | 0.16.0 124 | 125 | 126 | org.bstats 127 | bstats-bungeecord 128 | 3.1.0 129 | compile 130 | 131 | 132 | 133 | org.bstats 134 | bstats-velocity 135 | 3.1.0 136 | compile 137 | 138 | 139 | 140 | com.github.proxiodev.redisbungee 141 | RedisBungee-Bungee 142 | 0.13.0 143 | provided 144 | 145 | 146 | 147 | org.junit.jupiter 148 | junit-jupiter 149 | ${junit.jupiter.version} 150 | test 151 | 152 | 153 | org.mockito 154 | mockito-core 155 | ${org.mockito.version} 156 | test 157 | 158 | 159 | org.mockito 160 | mockito-junit-jupiter 161 | ${org.mockito.version} 162 | test 163 | 164 | 165 | io.rest-assured 166 | rest-assured 167 | 5.5.0 168 | test 169 | 170 | 171 | 172 | 173 | 174 | src/main/java 175 | src/test/java 176 | 177 | 178 | 179 | pl.project13.maven 180 | git-commit-id-plugin 181 | 4.9.10 182 | 183 | 184 | git-info 185 | 186 | revision 187 | 188 | 189 | 190 | 191 | true 192 | yyyy-MM-dd HH:mm:ss 193 | true 194 | true 195 | 196 | false 197 | false 198 | 7 199 | false 200 | 201 | 202 | 203 | 204 | org.apache.maven.plugins 205 | maven-shade-plugin 206 | 3.6.0 207 | 208 | 209 | package 210 | 211 | shade 212 | 213 | 214 | false 215 | 216 | 217 | 218 | ${project.artifactId} 219 | ${project.version} 220 | 221 | 222 | 223 | 224 | 225 | org.bstats 226 | 227 | org.akadia.prometheus.bstats 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | org.apache.maven.plugins 236 | maven-release-plugin 237 | 3.1.1 238 | 239 | [ci skip] 240 | @{project.version} 241 | 242 | 243 | 244 | maven-assembly-plugin 245 | 246 | 247 | src/main/assembly/package.xml 248 | 249 | 250 | 251 | 252 | build 253 | package 254 | 255 | single 256 | 257 | 258 | 259 | 260 | 261 | org.apache.maven.plugins 262 | maven-compiler-plugin 263 | ${maven-compiler-plugin.version} 264 | 265 | 17 266 | 267 | 268 | 269 | org.apache.maven.plugins 270 | maven-surefire-plugin 271 | ${maven-surefire-plugin.version} 272 | 273 | 274 | 275 | 276 | src/main/resources/ 277 | true 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /src/main/assembly/package.xml: -------------------------------------------------------------------------------- 1 | 4 | bin 5 | false 6 | 7 | zip 8 | 9 | 10 | 11 | ${project.build.directory}/${artifactId}-${version}.jar 12 | / 13 | BungeeCordPrometheusExporter.jar 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/MetricRegistry.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus; 2 | 3 | import org.akadia.prometheus.interfaces.Metric; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class MetricRegistry { 9 | private static final MetricRegistry INSTANCE = new MetricRegistry(); 10 | 11 | private final List metrics = new ArrayList<>(); 12 | 13 | private MetricRegistry() { 14 | 15 | } 16 | 17 | public static MetricRegistry getInstance() { 18 | return INSTANCE; 19 | } 20 | 21 | public void register(Metric metric) { 22 | this.metrics.add(metric); 23 | } 24 | 25 | void collectMetrics() { 26 | this.metrics.forEach(Metric::collect); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/MetricsServer.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus; 2 | 3 | import io.prometheus.client.Collector; 4 | import io.prometheus.client.CollectorRegistry; 5 | import io.prometheus.client.exporter.common.TextFormat; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.eclipse.jetty.http.HttpStatus; 9 | import org.eclipse.jetty.server.Request; 10 | import org.eclipse.jetty.server.Server; 11 | import org.eclipse.jetty.server.handler.AbstractHandler; 12 | import org.eclipse.jetty.server.handler.gzip.GzipHandler; 13 | 14 | import java.io.IOException; 15 | import java.net.InetSocketAddress; 16 | import java.util.Enumeration; 17 | 18 | public class MetricsServer { 19 | 20 | private final String host; 21 | private final int port; 22 | private final PrometheusExporter prometheusExporter; 23 | 24 | private Server server; 25 | 26 | public MetricsServer(String host, int port, PrometheusExporter prometheusExporter) { 27 | this.host = host; 28 | this.port = port; 29 | this.prometheusExporter = prometheusExporter; 30 | } 31 | 32 | public void start() throws Exception { 33 | GzipHandler gzipHandler = new GzipHandler(); 34 | gzipHandler.setHandler(new AbstractHandler() { 35 | @Override 36 | public void handle(String target, Request request, HttpServletRequest httpServletRequest, HttpServletResponse response) throws IOException { 37 | if (!target.equals("/metrics")) { 38 | response.sendError(HttpServletResponse.SC_NOT_FOUND); 39 | return; 40 | } 41 | 42 | try { 43 | MetricRegistry.getInstance().collectMetrics(); 44 | response.setStatus(HttpStatus.OK_200); 45 | response.setContentType(TextFormat.CONTENT_TYPE_004); 46 | Enumeration metricFamilySamplesEnumeration = CollectorRegistry.defaultRegistry.metricFamilySamples(); 47 | 48 | TextFormat.write004(response.getWriter(), CollectorRegistry.defaultRegistry.metricFamilySamples()); 49 | 50 | request.setHandled(true); 51 | } catch (IOException e) { 52 | prometheusExporter.warn("Failed to read server statistic: " + e.getMessage()); 53 | response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500); 54 | } 55 | 56 | } 57 | }); 58 | 59 | InetSocketAddress address = new InetSocketAddress(host, port); 60 | server = new Server(address); 61 | server.setHandler(gzipHandler); 62 | 63 | server.start(); 64 | } 65 | 66 | public void stop() throws Exception { 67 | if (server == null) { 68 | return; 69 | } 70 | 71 | server.stop(); 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/PrometheusExporter.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus; 2 | 3 | public interface PrometheusExporter { 4 | void info(String info); 5 | 6 | void warn(String warning); 7 | 8 | String getPrefix(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/PrometheusBungeeCordExporter.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord; 2 | 3 | import io.prometheus.client.CollectorRegistry; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.api.plugin.Plugin; 6 | import org.akadia.prometheus.MetricRegistry; 7 | import org.akadia.prometheus.MetricsServer; 8 | import org.akadia.prometheus.PrometheusExporter; 9 | import org.akadia.prometheus.bungeecord.listeners.LoginEventListener; 10 | import org.akadia.prometheus.bungeecord.listeners.PlayerChatEventListener; 11 | import org.akadia.prometheus.bungeecord.listeners.PlayerCommandEventListener; 12 | import org.akadia.prometheus.bungeecord.listeners.PlayerDisconnectEventListener; 13 | import org.akadia.prometheus.bungeecord.listeners.PlayerJoinedNetworkEventListener; 14 | import org.akadia.prometheus.bungeecord.listeners.PlayerKickEventListener; 15 | import org.akadia.prometheus.bungeecord.listeners.PlayerLeftNetworkEventListener; 16 | import org.akadia.prometheus.bungeecord.listeners.ProxyPingEventListener; 17 | import org.akadia.prometheus.bungeecord.metrics.InstalledNetworkPlugins; 18 | import org.akadia.prometheus.bungeecord.metrics.ManagedServers; 19 | import org.akadia.prometheus.bungeecord.metrics.OnlinePlayer; 20 | import org.akadia.prometheus.bungeecord.metrics.OnlinePlayerLatency; 21 | import org.akadia.prometheus.bungeecord.metrics.RedisBungeeOnlinePlayer; 22 | import org.akadia.prometheus.bungeecord.metrics.RedisBungeeOnlineProxies; 23 | import org.akadia.prometheus.config.ConfigManager; 24 | import org.akadia.prometheus.interfaces.Configurable; 25 | import org.akadia.prometheus.interfaces.CountableMetrics; 26 | import org.akadia.prometheus.interfaces.Metric; 27 | import org.akadia.prometheus.interfaces.MetricWrapper; 28 | import org.akadia.prometheus.metrics.JvmGarbageCollectorWrapper; 29 | import org.akadia.prometheus.metrics.JvmMemory; 30 | import org.akadia.prometheus.metrics.JvmThreadsWrapper; 31 | import org.bstats.bungeecord.Metrics; 32 | 33 | import java.io.IOException; 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | 37 | public class PrometheusBungeeCordExporter extends Plugin implements PrometheusExporter { 38 | String prefix; 39 | 40 | @Override 41 | public void onEnable() { 42 | try { 43 | ConfigManager configManager = new ConfigManager(getDataFolder()); 44 | startMetricsServer(configManager); 45 | } catch (IOException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | private void startMetricsServer(ConfigManager configManager) { 51 | if (configManager.getConfig().get("bstats").equals("true")) { 52 | try { 53 | new Metrics(this, 11269); 54 | } catch (IllegalStateException ex) { 55 | getLogger().info("bStats Metrics failed to start"); 56 | } 57 | } 58 | 59 | this.prefix = configManager.getConfig().getOrDefault("prefix", "bungeecord_"); 60 | 61 | String host = configManager.getConfig().getOrDefault("host", "127.0.0.1"); 62 | int port = Integer.parseInt(configManager.getConfig().getOrDefault("port", "9985")); 63 | 64 | MetricsServer server = new MetricsServer(host, port, this); 65 | 66 | List configurables = new ArrayList<>(); 67 | configurables.add(new LoginEventListener(this)); 68 | configurables.add(new PlayerDisconnectEventListener(this)); 69 | configurables.add(new PlayerChatEventListener(this)); 70 | configurables.add(new PlayerCommandEventListener(this)); 71 | configurables.add(new PlayerKickEventListener(this)); 72 | configurables.add(new ProxyPingEventListener(this)); 73 | configurables.add(new JvmGarbageCollectorWrapper(this)); 74 | configurables.add(new JvmMemory(this)); 75 | configurables.add(new JvmThreadsWrapper(this)); 76 | configurables.add(new OnlinePlayer(this)); 77 | configurables.add(new OnlinePlayerLatency(this)); 78 | configurables.add(new ManagedServers(this)); 79 | configurables.add(new InstalledNetworkPlugins(this)); 80 | 81 | configurables.add(new PlayerJoinedNetworkEventListener(this)); 82 | configurables.add(new PlayerLeftNetworkEventListener(this)); 83 | configurables.add(new RedisBungeeOnlinePlayer(this)); 84 | configurables.add(new RedisBungeeOnlineProxies(this)); 85 | 86 | for (Configurable configurable : configurables) { 87 | if (configManager.getConfig().getOrDefault(configurable.getConfigKey(), "true").equals("false")) { 88 | this.info(configurable.getConfigKey() + " is disabled in the config"); 89 | continue; 90 | } 91 | this.info(configurable.getConfigKey() + " is enabled in the config"); 92 | if (configurable instanceof CountableMetrics) { 93 | this.getProxy().getPluginManager().registerListener(this, (Listener) configurable); 94 | } else if (configurable instanceof MetricWrapper) { 95 | CollectorRegistry.defaultRegistry.register(((MetricWrapper) configurable).getCollector()); 96 | } else { 97 | MetricRegistry.getInstance().register((Metric) configurable); 98 | } 99 | } 100 | 101 | try { 102 | server.start(); 103 | this.info("Started Prometheus metrics endpoint at: " + host + ":" + port); 104 | } catch (Exception e) { 105 | this.warn("Could not start embedded Jetty server"); 106 | } 107 | 108 | this.info("Initialized completed"); 109 | } 110 | 111 | @Override 112 | public void info(String info) { 113 | this.getLogger().info(info); 114 | } 115 | 116 | @Override 117 | public void warn(String warning) { 118 | this.getLogger().warning(warning); 119 | } 120 | 121 | @Override 122 | public String getPrefix() { 123 | return this.prefix; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/LoginEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import net.md_5.bungee.api.event.LoginEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class LoginEventListener extends CountableMetrics implements Listener { 10 | 11 | public LoginEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onLoginEvent(LoginEvent event) { 17 | this.getCounter().inc(); 18 | } 19 | 20 | @Override 21 | public String getHelp() { 22 | return "the number of player logins in BungeeCord"; 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "player_connects"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/PlayerChatEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import net.md_5.bungee.api.event.ChatEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class PlayerChatEventListener extends CountableMetrics implements Listener { 10 | 11 | public PlayerChatEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onPlayerChatEvent(ChatEvent event) { 17 | if (event.isCommand()) { 18 | return; 19 | } 20 | this.getCounter().inc(); 21 | } 22 | 23 | @Override 24 | public String getHelp() { 25 | return "the number of player chat in BungeeCord"; 26 | } 27 | 28 | @Override 29 | public String getConfigKey() { 30 | return "player_chats"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/PlayerCommandEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import net.md_5.bungee.api.event.ChatEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class PlayerCommandEventListener extends CountableMetrics implements Listener { 10 | 11 | public PlayerCommandEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onPlayerCommandEvent(ChatEvent event) { 17 | if (!event.isCommand()) { 18 | return; 19 | } 20 | this.getCounter().inc(); 21 | } 22 | 23 | @Override 24 | public String getHelp() { 25 | return "the number of player commands in BungeeCord"; 26 | } 27 | 28 | @Override 29 | public String getConfigKey() { 30 | return "player_commands"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/PlayerDisconnectEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import net.md_5.bungee.api.event.PlayerDisconnectEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class PlayerDisconnectEventListener extends CountableMetrics implements Listener { 10 | 11 | public PlayerDisconnectEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onPlayerDisconnectEvent(PlayerDisconnectEvent event) { 17 | this.getCounter().inc(); 18 | } 19 | 20 | @Override 21 | public String getHelp() { 22 | return "the number of player disconnects in BungeeCord"; 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "player_disconnects"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/PlayerJoinedNetworkEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class PlayerJoinedNetworkEventListener extends CountableMetrics implements Listener { 10 | 11 | public PlayerJoinedNetworkEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onPlayerDisconnectEvent(PlayerJoinedNetworkEvent event) { 17 | this.getCounter().inc(); 18 | } 19 | 20 | @Override 21 | public String getHelp() { 22 | return "the number of players joined in redisbungee"; 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "redis_player_connects"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/PlayerKickEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import net.md_5.bungee.api.event.ServerKickEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class PlayerKickEventListener extends CountableMetrics implements Listener { 10 | 11 | public PlayerKickEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onPlayerDisconnectEvent(ServerKickEvent event) { 17 | this.getCounter().inc(); 18 | } 19 | 20 | @Override 21 | public String getHelp() { 22 | return "the number of player kicked in BungeeCord"; 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "player_kicks"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/PlayerLeftNetworkEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class PlayerLeftNetworkEventListener extends CountableMetrics implements Listener { 10 | 11 | public PlayerLeftNetworkEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onPlayerDisconnectEvent(PlayerLeftNetworkEvent event) { 17 | this.getCounter().inc(); 18 | } 19 | 20 | @Override 21 | public String getHelp() { 22 | return "the number of players disconnects in redisbungee"; 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "redis_player_disconnects"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/listeners/ProxyPingEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.listeners; 2 | 3 | import net.md_5.bungee.api.event.ProxyPingEvent; 4 | import net.md_5.bungee.api.plugin.Listener; 5 | import net.md_5.bungee.event.EventHandler; 6 | import org.akadia.prometheus.PrometheusExporter; 7 | import org.akadia.prometheus.interfaces.CountableMetrics; 8 | 9 | public class ProxyPingEventListener extends CountableMetrics implements Listener { 10 | 11 | public ProxyPingEventListener(PrometheusExporter plugin) { 12 | super(plugin); 13 | } 14 | 15 | @EventHandler 16 | public void onProxyPingEvent(ProxyPingEvent event) { 17 | this.getCounter().inc(); 18 | } 19 | 20 | @Override 21 | public String getHelp() { 22 | return "the number of server list pings in BungeeCord"; 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "server_list_pings"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/metrics/InstalledNetworkPlugins.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.metrics; 2 | 3 | import org.akadia.prometheus.bungeecord.PrometheusBungeeCordExporter; 4 | import org.akadia.prometheus.interfaces.GauageMetric; 5 | 6 | public class InstalledNetworkPlugins extends GauageMetric { 7 | 8 | public InstalledNetworkPlugins(PrometheusBungeeCordExporter plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public void doCollect() { 14 | this.getGauge() 15 | .set( 16 | ((PrometheusBungeeCordExporter) getPlugin()) 17 | .getProxy() 18 | .getPluginManager() 19 | .getPlugins().size() 20 | ); 21 | } 22 | 23 | @Override 24 | public String getConfigKey() { 25 | return "installed_network_plugins"; 26 | } 27 | 28 | @Override 29 | public String getHelp() { 30 | return "the number of installed network plugins in BungeeCord"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/metrics/ManagedServers.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.metrics; 2 | 3 | import org.akadia.prometheus.bungeecord.PrometheusBungeeCordExporter; 4 | import org.akadia.prometheus.interfaces.GauageMetric; 5 | 6 | public class ManagedServers extends GauageMetric { 7 | 8 | public ManagedServers(PrometheusBungeeCordExporter plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public void doCollect() { 14 | this.getGauge() 15 | .set( 16 | ((PrometheusBungeeCordExporter) getPlugin()) 17 | .getProxy() 18 | .getServers() 19 | .size() 20 | ); 21 | } 22 | 23 | @Override 24 | public String getConfigKey() { 25 | return "managed_servers"; 26 | } 27 | 28 | @Override 29 | public String getHelp() { 30 | return "the number of managed servers in BungeeCord"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/metrics/OnlinePlayer.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.metrics; 2 | 3 | import net.md_5.bungee.api.ProxyServer; 4 | import org.akadia.prometheus.bungeecord.PrometheusBungeeCordExporter; 5 | import org.akadia.prometheus.interfaces.GauageMetric; 6 | 7 | public class OnlinePlayer extends GauageMetric { 8 | 9 | public OnlinePlayer(PrometheusBungeeCordExporter plugin) { 10 | super(plugin); 11 | } 12 | 13 | @Override 14 | public void doCollect() { 15 | this.getGauge().clear(); 16 | 17 | ProxyServer proxy = ((PrometheusBungeeCordExporter) getPlugin()).getProxy(); 18 | proxy.getServers().forEach((key, value) -> { 19 | this.getGauge().labels(key, "", "").set(0); 20 | value.getPlayers().forEach(proxiedPlayer -> 21 | this.getGauge().labels(key, proxiedPlayer.getName(), Boolean.toString(proxy.getConfig().isOnlineMode())).set(1)); 22 | }); 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "online_player"; 28 | } 29 | 30 | @Override 31 | public String getHelp() { 32 | return "the name of the online player in BungeeCord"; 33 | } 34 | 35 | @Override 36 | public String[] getLabels() { 37 | return new String[]{"server", "player", "online_mode"}; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/metrics/OnlinePlayerLatency.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.metrics; 2 | 3 | import org.akadia.prometheus.bungeecord.PrometheusBungeeCordExporter; 4 | import org.akadia.prometheus.interfaces.SummaryMetric; 5 | 6 | public class OnlinePlayerLatency extends SummaryMetric { 7 | 8 | public OnlinePlayerLatency(PrometheusBungeeCordExporter plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public void doCollect() { 14 | ((PrometheusBungeeCordExporter) getPlugin()) 15 | .getProxy() 16 | .getPlayers() 17 | .forEach(proxiedPlayer -> 18 | this.getSummary().labels(proxiedPlayer.getName()).observe(proxiedPlayer.getPing()) 19 | ); 20 | } 21 | 22 | @Override 23 | public String getConfigKey() { 24 | return "online_player_latency"; 25 | } 26 | 27 | @Override 28 | public String getHelp() { 29 | return "the latency of an online player in BungeeCord"; 30 | } 31 | 32 | @Override 33 | public String[] getLabels() { 34 | return new String[]{"name"}; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/metrics/RedisBungeeOnlinePlayer.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.metrics; 2 | 3 | import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI; 4 | import org.akadia.prometheus.PrometheusExporter; 5 | import org.akadia.prometheus.interfaces.GauageMetric; 6 | 7 | public class RedisBungeeOnlinePlayer extends GauageMetric { 8 | 9 | public RedisBungeeOnlinePlayer(PrometheusExporter plugin) { 10 | super(plugin); 11 | } 12 | 13 | @Override 14 | public void doCollect() { 15 | this.getGauge().clear(); 16 | RedisBungeeAPI.getRedisBungeeApi() 17 | .getServerToPlayers() 18 | .asMap() 19 | .forEach((server, players) -> { 20 | this.getGauge() 21 | .labels(server, "") 22 | .set(0); 23 | players.forEach(player -> { 24 | this.getGauge() 25 | .labels(server, player.toString()) 26 | .set(1); 27 | }); 28 | }); 29 | } 30 | 31 | @Override 32 | public String getConfigKey() { 33 | return "redis_online_player"; 34 | } 35 | 36 | @Override 37 | public String getHelp() { 38 | return "the name of online redisbungee player"; 39 | } 40 | 41 | @Override 42 | public String[] getLabels() { 43 | return new String[]{"server", "player"}; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/bungeecord/metrics/RedisBungeeOnlineProxies.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.bungeecord.metrics; 2 | 3 | import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI; 4 | import org.akadia.prometheus.PrometheusExporter; 5 | import org.akadia.prometheus.interfaces.GauageMetric; 6 | 7 | public class RedisBungeeOnlineProxies extends GauageMetric { 8 | 9 | public RedisBungeeOnlineProxies(PrometheusExporter plugin) { 10 | super(plugin); 11 | } 12 | 13 | @Override 14 | public void doCollect() { 15 | this.getGauge().set(RedisBungeeAPI.getRedisBungeeApi().getAllProxies().size()); 16 | } 17 | 18 | @Override 19 | public String getConfigKey() { 20 | return "redis_bungee_online_proxies"; 21 | } 22 | 23 | @Override 24 | public String getHelp() { 25 | return "the number of online redisbungee proxy"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/config/ConfigManager.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.config; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.Reader; 9 | import java.nio.file.Files; 10 | import java.util.Map; 11 | 12 | public class ConfigManager { 13 | private static final String CONFIG_NAME = "config.json"; 14 | private final Map config; 15 | 16 | public ConfigManager(File dataFolder) throws IOException { 17 | File configFile = createConfigFile(dataFolder, CONFIG_NAME, true); 18 | Reader reader = Files.newBufferedReader(configFile.toPath()); 19 | 20 | Gson gson = new Gson(); 21 | // convert JSON file to map 22 | config = gson.fromJson(reader, Map.class); 23 | } 24 | 25 | public Map getConfig() { 26 | return config; 27 | } 28 | 29 | public File createConfigFile(File dataFolder, String CONFIG_NAME, boolean copyFromResource) { 30 | File saveTo = null; 31 | try { 32 | if (!dataFolder.exists()) { 33 | dataFolder.mkdir(); 34 | } 35 | 36 | saveTo = new File(dataFolder, CONFIG_NAME); 37 | if (!saveTo.exists()) { 38 | if (copyFromResource) { 39 | InputStream in = this.getClass().getClassLoader().getResourceAsStream(CONFIG_NAME); 40 | Files.copy(in, saveTo.toPath()); 41 | } else { 42 | saveTo.createNewFile(); 43 | } 44 | } 45 | } catch (IOException ex) { 46 | ex.printStackTrace(); 47 | } 48 | return saveTo; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/interfaces/Configurable.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.interfaces; 2 | 3 | public interface Configurable { 4 | String getConfigKey(); 5 | 6 | String getHelp(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/interfaces/CountableMetrics.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.interfaces; 2 | 3 | import io.prometheus.client.Counter; 4 | import org.akadia.prometheus.PrometheusExporter; 5 | import org.akadia.prometheus.utils.Util; 6 | 7 | public abstract class CountableMetrics extends Metric { 8 | private final Counter counter; 9 | 10 | public CountableMetrics(PrometheusExporter plugin) { 11 | super(plugin); 12 | 13 | this.counter = Counter.build() 14 | .name(Util.prefix(plugin.getPrefix(), this.getConfigKey())) 15 | .labelNames(this.getLabels()) 16 | .help(this.getHelp()) 17 | .create() 18 | .register(); 19 | } 20 | 21 | @Override 22 | public void doCollect() { 23 | } 24 | 25 | public Counter getCounter() { 26 | return counter; 27 | } 28 | 29 | public abstract String getConfigKey(); 30 | 31 | public abstract String getHelp(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/interfaces/GauageMetric.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.interfaces; 2 | 3 | import io.prometheus.client.Gauge; 4 | import org.akadia.prometheus.PrometheusExporter; 5 | import org.akadia.prometheus.utils.Util; 6 | 7 | public abstract class GauageMetric extends Metric { 8 | 9 | private final Gauge gauge; 10 | 11 | public GauageMetric(PrometheusExporter plugin) { 12 | super(plugin); 13 | 14 | this.gauge = Gauge.build() 15 | .name(Util.prefix(plugin.getPrefix(), this.getConfigKey())) 16 | .help(this.getHelp()) 17 | .labelNames(this.getLabels()) 18 | .create() 19 | .register(); 20 | } 21 | 22 | public Gauge getGauge() { 23 | return gauge; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/interfaces/Metric.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.interfaces; 2 | 3 | import org.akadia.prometheus.PrometheusExporter; 4 | 5 | import java.io.PrintWriter; 6 | import java.io.StringWriter; 7 | 8 | public abstract class Metric implements Configurable { 9 | 10 | private final PrometheusExporter plugin; 11 | 12 | public Metric(PrometheusExporter plugin) { 13 | this.plugin = plugin; 14 | } 15 | 16 | public PrometheusExporter getPlugin() { 17 | return plugin; 18 | } 19 | 20 | public void collect() { 21 | try { 22 | doCollect(); 23 | } catch (Exception e) { 24 | logException(e); 25 | } 26 | } 27 | 28 | public abstract void doCollect(); 29 | 30 | private void logException(Exception e) { 31 | final String className = this.getClass().getSimpleName(); 32 | 33 | StringWriter sw = new StringWriter(); 34 | PrintWriter pw = new PrintWriter(sw); 35 | e.printStackTrace(pw); 36 | 37 | plugin.warn(String.format("Failed to collect metric '%s': %s", 38 | className, sw)); 39 | 40 | plugin.warn(className + " collect:" + e); 41 | } 42 | 43 | public abstract String getConfigKey(); 44 | 45 | public abstract String getHelp(); 46 | 47 | public String[] getLabels() { 48 | return new String[]{}; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/interfaces/MetricWrapper.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.interfaces; 2 | 3 | import io.prometheus.client.Collector; 4 | import org.akadia.prometheus.PrometheusExporter; 5 | 6 | public abstract class MetricWrapper extends Metric { 7 | private final Collector collector; 8 | 9 | public MetricWrapper(PrometheusExporter plugin, Collector collector) { 10 | super(plugin); 11 | this.collector = collector; 12 | } 13 | 14 | public Collector getCollector() { 15 | return collector; 16 | } 17 | 18 | public abstract String getConfigKey(); 19 | 20 | public abstract String getHelp(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/interfaces/SummaryMetric.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.interfaces; 2 | 3 | import io.prometheus.client.Summary; 4 | import org.akadia.prometheus.PrometheusExporter; 5 | import org.akadia.prometheus.utils.Util; 6 | 7 | public abstract class SummaryMetric extends Metric { 8 | 9 | private final Summary summary; 10 | 11 | public SummaryMetric(PrometheusExporter plugin) { 12 | super(plugin); 13 | 14 | this.summary = Summary.build() 15 | .name(Util.prefix(plugin.getPrefix(), this.getConfigKey())) 16 | .help(this.getHelp()) 17 | .quantile(0.95, 0.005) // 0.95 quantile with 0.005 allowed error 18 | .labelNames(this.getLabels()) 19 | .create() 20 | .register(); 21 | } 22 | 23 | public Summary getSummary() { 24 | return summary; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/metrics/JvmGarbageCollectorWrapper.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.metrics; 2 | 3 | import io.prometheus.client.Collector; 4 | import io.prometheus.client.hotspot.GarbageCollectorExports; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.MetricWrapper; 7 | import org.akadia.prometheus.utils.Util; 8 | 9 | import java.util.List; 10 | 11 | public class JvmGarbageCollectorWrapper extends MetricWrapper { 12 | 13 | public JvmGarbageCollectorWrapper(PrometheusExporter plugin) { 14 | super(plugin, new GarbageCollectorExportsCollector(plugin.getPrefix())); 15 | } 16 | 17 | @Override 18 | public void doCollect() { 19 | } 20 | 21 | @Override 22 | public String getConfigKey() { 23 | return "jvm_gc"; 24 | } 25 | 26 | @Override 27 | public String getHelp() { 28 | return "JVM garbage collection"; 29 | } 30 | 31 | private static class GarbageCollectorExportsCollector extends Collector { 32 | private static final GarbageCollectorExports garbageCollectorExports = new GarbageCollectorExports(); 33 | 34 | private final String prefix; 35 | 36 | public GarbageCollectorExportsCollector(String prefix) { 37 | this.prefix = prefix; 38 | } 39 | 40 | @Override 41 | public List collect() { 42 | return Util.prefixFromCollector(garbageCollectorExports, prefix); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/metrics/JvmMemory.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.metrics; 2 | 3 | import org.akadia.prometheus.PrometheusExporter; 4 | import org.akadia.prometheus.interfaces.GauageMetric; 5 | 6 | public class JvmMemory extends GauageMetric { 7 | 8 | 9 | public JvmMemory(PrometheusExporter plugin) { 10 | super(plugin); 11 | } 12 | 13 | @Override 14 | public void doCollect() { 15 | this.getGauge().labels("max").set(Runtime.getRuntime().maxMemory()); 16 | this.getGauge().labels("free").set(Runtime.getRuntime().freeMemory()); 17 | this.getGauge().labels("allocated").set(Runtime.getRuntime().totalMemory()); 18 | this.getGauge().labels("used").set(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); 19 | } 20 | 21 | @Override 22 | public String getConfigKey() { 23 | return "jvm_memory"; 24 | } 25 | 26 | @Override 27 | public String getHelp() { 28 | return "JVM memory usage"; 29 | } 30 | 31 | @Override 32 | public String[] getLabels() { 33 | return new String[]{"type"}; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/metrics/JvmThreadsWrapper.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.metrics; 2 | 3 | import io.prometheus.client.Collector; 4 | import io.prometheus.client.hotspot.ThreadExports; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.MetricWrapper; 7 | import org.akadia.prometheus.utils.Util; 8 | 9 | import java.util.List; 10 | 11 | public class JvmThreadsWrapper extends MetricWrapper { 12 | 13 | public JvmThreadsWrapper(PrometheusExporter plugin) { 14 | super(plugin, new ThreadExportsCollector(plugin.getPrefix())); 15 | } 16 | 17 | @Override 18 | public void doCollect() { 19 | } 20 | 21 | 22 | @Override 23 | public String getConfigKey() { 24 | return "jvm_threads"; 25 | } 26 | 27 | @Override 28 | public String getHelp() { 29 | return "JVM threads usage"; 30 | } 31 | 32 | private static class ThreadExportsCollector extends Collector { 33 | private static final ThreadExports threadExports = new ThreadExports(); 34 | private final String prefix; 35 | 36 | public ThreadExportsCollector(String prefix) { 37 | this.prefix = prefix; 38 | } 39 | 40 | @Override 41 | public List collect() { 42 | return Util.prefixFromCollector(threadExports, prefix); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/utils/Util.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.utils; 2 | 3 | import io.prometheus.client.Collector; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Util { 9 | 10 | public static String prefix(String prefix, String name) { 11 | return prefix + name; 12 | } 13 | 14 | public static List prefixFromCollector(Collector collector, String prefix) { 15 | List collected = collector.collect(); 16 | List mfs = new ArrayList<>(); 17 | 18 | for (Collector.MetricFamilySamples mSample : collected) { 19 | List samples = new ArrayList<>(mSample.samples.size()); 20 | for (Collector.MetricFamilySamples.Sample sample : mSample.samples) { 21 | samples.add(new Collector.MetricFamilySamples.Sample(Util.prefix(prefix, sample.name), sample.labelNames, sample.labelValues, sample.value)); 22 | } 23 | 24 | Collector.MetricFamilySamples prefixed = new Collector.MetricFamilySamples(Util.prefix(prefix, mSample.name), mSample.type, mSample.help, samples); 25 | mfs.add(prefixed); 26 | } 27 | 28 | return mfs; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/PrometheusVelocityExporter.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.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.plugin.Plugin; 7 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 8 | import com.velocitypowered.api.proxy.ProxyServer; 9 | import io.prometheus.client.CollectorRegistry; 10 | import org.akadia.prometheus.MetricRegistry; 11 | import org.akadia.prometheus.MetricsServer; 12 | import org.akadia.prometheus.PrometheusExporter; 13 | import org.akadia.prometheus.config.ConfigManager; 14 | import org.akadia.prometheus.interfaces.Configurable; 15 | import org.akadia.prometheus.interfaces.CountableMetrics; 16 | import org.akadia.prometheus.interfaces.Metric; 17 | import org.akadia.prometheus.interfaces.MetricWrapper; 18 | import org.akadia.prometheus.metrics.JvmGarbageCollectorWrapper; 19 | import org.akadia.prometheus.metrics.JvmMemory; 20 | import org.akadia.prometheus.metrics.JvmThreadsWrapper; 21 | import org.akadia.prometheus.velocity.listeners.LoginEventListener; 22 | import org.akadia.prometheus.velocity.listeners.PlayerChatEventListener; 23 | import org.akadia.prometheus.velocity.listeners.PlayerDisconnectEventListener; 24 | import org.akadia.prometheus.velocity.listeners.ProxyPingEventListener; 25 | import org.akadia.prometheus.velocity.listeners.PlayerCommandEventListener; 26 | import org.akadia.prometheus.velocity.metrics.InstalledNetworkPlugins; 27 | import org.akadia.prometheus.velocity.metrics.ManagedServers; 28 | import org.akadia.prometheus.velocity.metrics.OnlinePlayer; 29 | import org.akadia.prometheus.velocity.metrics.OnlinePlayersLatency; 30 | import org.bstats.velocity.Metrics; 31 | import org.slf4j.Logger; 32 | 33 | import java.io.IOException; 34 | import java.nio.file.Path; 35 | import java.util.ArrayList; 36 | import java.util.List; 37 | 38 | @Plugin(id = "velocity-prometheus-exporter", name = "Velocity Prometheus Exporter", version = "1.0.0", authors = "akadia") 39 | public class PrometheusVelocityExporter implements PrometheusExporter { 40 | @Inject 41 | @DataDirectory 42 | public Path configDir; 43 | @Inject 44 | private Metrics.Factory metricsFactory; 45 | @Inject 46 | private ProxyServer proxyServer; 47 | 48 | @Inject 49 | private Logger logger; 50 | private String prefix; 51 | 52 | public ProxyServer getProxyServer() { 53 | return proxyServer; 54 | } 55 | 56 | @Subscribe 57 | public void onProxyInitialization(ProxyInitializeEvent event) { 58 | try { 59 | ConfigManager configManager = new ConfigManager(configDir.toFile()); 60 | startMetricsServer(configManager); 61 | 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | 67 | private void startMetricsServer(ConfigManager configManager) { 68 | if (configManager.getConfig().get("bstats").equals("true")) { 69 | try { 70 | metricsFactory.make(this, 11269); 71 | } catch (IllegalStateException ex) { 72 | this.info("bStats Metrics failed to start"); 73 | } 74 | } 75 | 76 | this.prefix = configManager.getConfig().getOrDefault("prefix", "bungeecord_"); 77 | 78 | String host = configManager.getConfig().getOrDefault("host", "127.0.0.1"); 79 | int port = Integer.parseInt(configManager.getConfig().getOrDefault("port", "9985")); 80 | 81 | MetricsServer server = new MetricsServer(host, port, this); 82 | 83 | List configurables = new ArrayList<>(); 84 | configurables.add(new LoginEventListener(this)); 85 | configurables.add(new PlayerDisconnectEventListener(this)); 86 | configurables.add(new PlayerChatEventListener(this)); 87 | configurables.add(new PlayerCommandEventListener(this)); 88 | configurables.add(new ProxyPingEventListener(this)); 89 | configurables.add(new JvmGarbageCollectorWrapper(this)); 90 | configurables.add(new JvmMemory(this)); 91 | configurables.add(new JvmThreadsWrapper(this)); 92 | configurables.add(new OnlinePlayer(this)); 93 | configurables.add(new OnlinePlayersLatency(this)); 94 | configurables.add(new ManagedServers(this)); 95 | configurables.add(new InstalledNetworkPlugins(this)); 96 | 97 | for (Configurable configurable : configurables) { 98 | if (configManager.getConfig().getOrDefault(configurable.getConfigKey(), "true").equals("false")) { 99 | logger.info(configurable.getConfigKey() + " is disabled in the config"); 100 | continue; 101 | } 102 | this.info(configurable.getConfigKey() + " is enabled in the config"); 103 | if (configurable instanceof CountableMetrics) { 104 | proxyServer.getEventManager().register(this, configurable); 105 | } else if (configurable instanceof MetricWrapper) { 106 | CollectorRegistry.defaultRegistry.register(((MetricWrapper) configurable).getCollector()); 107 | } else { 108 | MetricRegistry.getInstance().register((Metric) configurable); 109 | } 110 | } 111 | 112 | try { 113 | server.start(); 114 | this.info("Started Prometheus metrics endpoint at: " + host + ":" + port); 115 | } catch (Exception e) { 116 | this.warn("Could not start embedded Jetty server"); 117 | } 118 | 119 | this.info("Initialized completed"); 120 | } 121 | 122 | 123 | @Override 124 | public void info(String info) { 125 | this.logger.info(info); 126 | } 127 | 128 | @Override 129 | public void warn(String warning) { 130 | this.logger.warn(warning); 131 | } 132 | 133 | @Override 134 | public String getPrefix() { 135 | return this.prefix; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/listeners/LoginEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.listeners; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.event.connection.LoginEvent; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.CountableMetrics; 7 | 8 | public class LoginEventListener extends CountableMetrics { 9 | 10 | public LoginEventListener(PrometheusExporter plugin) { 11 | super(plugin); 12 | } 13 | 14 | @Subscribe 15 | public void onLoginEvent(LoginEvent event) { 16 | this.getCounter().inc(); 17 | } 18 | 19 | @Override 20 | public String getHelp() { 21 | return "the number of player logins in Velocity"; 22 | } 23 | 24 | @Override 25 | public String getConfigKey() { 26 | return "player_connects"; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/listeners/PlayerChatEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.listeners; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.event.player.PlayerChatEvent; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.CountableMetrics; 7 | 8 | public class PlayerChatEventListener extends CountableMetrics { 9 | 10 | public PlayerChatEventListener(PrometheusExporter plugin) { 11 | super(plugin); 12 | } 13 | 14 | @Subscribe 15 | public void onPlayerChatEvent(PlayerChatEvent event) { 16 | this.getCounter().inc(); 17 | } 18 | 19 | @Override 20 | public String getHelp() { 21 | return "the number of player chats in Velocity"; 22 | } 23 | 24 | @Override 25 | public String getConfigKey() { 26 | return "player_chats"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/listeners/PlayerCommandEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.listeners; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.event.command.CommandExecuteEvent; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.CountableMetrics; 7 | 8 | public class PlayerCommandEventListener extends CountableMetrics { 9 | 10 | public PlayerCommandEventListener(PrometheusExporter plugin) { 11 | super(plugin); 12 | } 13 | 14 | @Subscribe 15 | public void onPlayerCommandEvent(CommandExecuteEvent event) { 16 | this.getCounter().inc(); 17 | } 18 | 19 | @Override 20 | public String getHelp() { 21 | return "the number of player commands in Velocity"; 22 | } 23 | 24 | @Override 25 | public String getConfigKey() { 26 | return "player_commands"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/listeners/PlayerDisconnectEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.listeners; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.event.connection.DisconnectEvent; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.CountableMetrics; 7 | 8 | public class PlayerDisconnectEventListener extends CountableMetrics { 9 | 10 | public PlayerDisconnectEventListener(PrometheusExporter plugin) { 11 | super(plugin); 12 | } 13 | 14 | @Subscribe 15 | public void onPlayerDisconnectEvent(DisconnectEvent event) { 16 | this.getCounter().inc(); 17 | } 18 | 19 | @Override 20 | public String getHelp() { 21 | return "the number of player disconnects in Velocity"; 22 | } 23 | 24 | @Override 25 | public String getConfigKey() { 26 | return "player_disconnects"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/listeners/PlayerKickEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.listeners; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.event.player.KickedFromServerEvent; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.CountableMetrics; 7 | 8 | public class PlayerKickEventListener extends CountableMetrics { 9 | 10 | public PlayerKickEventListener(PrometheusExporter plugin) { 11 | super(plugin); 12 | } 13 | 14 | @Subscribe 15 | public void onPlayerDisconnectEvent(KickedFromServerEvent event) { 16 | this.getCounter().inc(); 17 | } 18 | 19 | @Override 20 | public String getHelp() { 21 | return "the number of player kicked in Velocity"; 22 | } 23 | 24 | @Override 25 | public String getConfigKey() { 26 | return "player_kicks"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/listeners/ProxyPingEventListener.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.listeners; 2 | 3 | import com.velocitypowered.api.event.Subscribe; 4 | import com.velocitypowered.api.event.proxy.ProxyPingEvent; 5 | import org.akadia.prometheus.PrometheusExporter; 6 | import org.akadia.prometheus.interfaces.CountableMetrics; 7 | 8 | public class ProxyPingEventListener extends CountableMetrics { 9 | 10 | public ProxyPingEventListener(PrometheusExporter plugin) { 11 | super(plugin); 12 | } 13 | 14 | @Subscribe 15 | public void onProxyPingEvent(ProxyPingEvent event) { 16 | this.getCounter().inc(); 17 | } 18 | 19 | @Override 20 | public String getHelp() { 21 | return "the number of server list pings in Velocity"; 22 | } 23 | 24 | @Override 25 | public String getConfigKey() { 26 | return "server_list_pings"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/metrics/InstalledNetworkPlugins.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.metrics; 2 | 3 | import org.akadia.prometheus.interfaces.GauageMetric; 4 | import org.akadia.prometheus.velocity.PrometheusVelocityExporter; 5 | 6 | public class InstalledNetworkPlugins extends GauageMetric { 7 | 8 | public InstalledNetworkPlugins(PrometheusVelocityExporter plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public void doCollect() { 14 | this.getGauge().set(((PrometheusVelocityExporter) getPlugin()).getProxyServer().getPluginManager().getPlugins().size()); 15 | } 16 | 17 | @Override 18 | public String getConfigKey() { 19 | return "installed_network_plugins"; 20 | } 21 | 22 | @Override 23 | public String getHelp() { 24 | return "the number of installed network plugins in Velocity"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/metrics/ManagedServers.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.metrics; 2 | 3 | import org.akadia.prometheus.interfaces.GauageMetric; 4 | import org.akadia.prometheus.velocity.PrometheusVelocityExporter; 5 | 6 | public class ManagedServers extends GauageMetric { 7 | 8 | public ManagedServers(PrometheusVelocityExporter plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public void doCollect() { 14 | this.getGauge().set(((PrometheusVelocityExporter) getPlugin()).getProxyServer().getAllServers().size()); 15 | } 16 | 17 | @Override 18 | public String getConfigKey() { 19 | return "managed_servers"; 20 | } 21 | 22 | @Override 23 | public String getHelp() { 24 | return "the number of managed servers in Velocity"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/metrics/OnlinePlayer.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.metrics; 2 | 3 | import org.akadia.prometheus.interfaces.GauageMetric; 4 | import org.akadia.prometheus.velocity.PrometheusVelocityExporter; 5 | 6 | public class OnlinePlayer extends GauageMetric { 7 | 8 | public OnlinePlayer(PrometheusVelocityExporter plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public void doCollect() { 14 | this.getGauge().clear(); 15 | 16 | 17 | ((PrometheusVelocityExporter) getPlugin()).getProxyServer().getAllServers().forEach(registeredServer -> { 18 | String serverName = registeredServer.getServerInfo().getName(); 19 | this.getGauge().labels(serverName, "", "").set(0); 20 | registeredServer.getPlayersConnected().forEach(player -> 21 | this.getGauge().labels(serverName, player.getUsername(), Boolean.toString(player.isOnlineMode())).set(1)); 22 | }); 23 | } 24 | 25 | @Override 26 | public String getConfigKey() { 27 | return "online_player"; 28 | } 29 | 30 | @Override 31 | public String getHelp() { 32 | return "the name of the online player in Velocity"; 33 | } 34 | 35 | @Override 36 | public String[] getLabels() { 37 | return new String[]{"server", "player", "online_mode"}; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/akadia/prometheus/velocity/metrics/OnlinePlayersLatency.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.velocity.metrics; 2 | 3 | import org.akadia.prometheus.interfaces.SummaryMetric; 4 | import org.akadia.prometheus.velocity.PrometheusVelocityExporter; 5 | 6 | public class OnlinePlayersLatency extends SummaryMetric { 7 | 8 | public OnlinePlayersLatency(PrometheusVelocityExporter plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public void doCollect() { 14 | ((PrometheusVelocityExporter) getPlugin()).getProxyServer().getAllPlayers().forEach(player -> this.getSummary().labels(player.getUsername()).observe(player.getPing())); 15 | } 16 | 17 | 18 | @Override 19 | public String getConfigKey() { 20 | return "online_player_latency"; 21 | } 22 | 23 | @Override 24 | public String getHelp() { 25 | return "the latency of an online player in Velocity"; 26 | } 27 | 28 | @Override 29 | public String[] getLabels() { 30 | return new String[]{"name"}; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: ${project.name} 2 | version: ${project.version} 3 | author: akadia 4 | authors: [ akdaia, sldk ] 5 | main: org.akadia.prometheus.bungeecord.PrometheusBungeeCordExporter 6 | website: akadia.org 7 | description: ${project.description} 8 | -------------------------------------------------------------------------------- /src/main/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bstats": "true", 3 | "host": "0.0.0.0", 4 | "port": "9985", 5 | "prefix": "bungeecord_", 6 | "jvm_gc": "true", 7 | "jvm_memory": "true", 8 | "jvm_threads": "true", 9 | "player_connects": "true", 10 | "player_disconnects": "true", 11 | "player_kicks": "true", 12 | "player_chats": "true", 13 | "player_commands": "true", 14 | "server_list_pings": "true", 15 | "managed_servers": "true", 16 | "installed_network_plugins": "true", 17 | "online_player": "true", 18 | "online_player_latency": "true", 19 | "redis_player_connects": "false", 20 | "redis_player_disconnects": "false", 21 | "redis_online_player": "false", 22 | "redis_bungee_online_proxies": "false" 23 | } -------------------------------------------------------------------------------- /src/main/resources/velocity-plugin.json: -------------------------------------------------------------------------------- 1 | {"id":"velocity-prometheus-exporter","name":"Velocity Prometheus Exporter","version":"1.0.0","authors":["akadia"],"dependencies":[],"main":"org.akadia.prometheus.velocity.PrometheusVelocityExporter"} -------------------------------------------------------------------------------- /src/test/java/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weihao/bungeecord-prometheus-exporter/993acbc55dd597cefb23b621f83906440904eec0/src/test/java/.gitignore -------------------------------------------------------------------------------- /src/test/java/org/akadia/prometheus/exporter/PrometheusExporterTest.java: -------------------------------------------------------------------------------- 1 | package org.akadia.prometheus.exporter; 2 | 3 | 4 | import io.prometheus.client.CollectorRegistry; 5 | import io.prometheus.client.Counter; 6 | import io.prometheus.client.exporter.common.TextFormat; 7 | import io.restassured.RestAssured; 8 | import org.akadia.prometheus.MetricsServer; 9 | import org.akadia.prometheus.PrometheusExporter; 10 | import org.eclipse.jetty.http.HttpStatus; 11 | import org.eclipse.jetty.util.URIUtil; 12 | import org.junit.jupiter.api.AfterEach; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.extension.ExtendWith; 16 | import org.mockito.Mock; 17 | import org.mockito.junit.jupiter.MockitoExtension; 18 | 19 | import java.io.IOException; 20 | import java.net.ServerSocket; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | 24 | @ExtendWith(MockitoExtension.class) 25 | public class PrometheusExporterTest { 26 | 27 | @Mock 28 | private PrometheusExporter exporterMock; 29 | 30 | private int metricsServerPort; 31 | private MetricsServer metricsServer; 32 | 33 | @BeforeEach 34 | void setup() throws Exception { 35 | CollectorRegistry.defaultRegistry.clear(); 36 | metricsServerPort = getRandomFreePort(); 37 | metricsServer = new MetricsServer("localhost", metricsServerPort, exporterMock); 38 | metricsServer.start(); 39 | } 40 | 41 | private int getRandomFreePort() throws IOException { 42 | try (ServerSocket serverSocket = new ServerSocket(0)) { 43 | return serverSocket.getLocalPort(); 44 | } 45 | } 46 | 47 | @AfterEach 48 | void cleanup() throws Exception { 49 | metricsServer.stop(); 50 | } 51 | 52 | @Test 53 | void metrics_server_should_return_valid_prometheus_response() { 54 | mockPrometheusCounter("bungeecord_online_players", "This is a mock metric of online bungeecord players", 233); 55 | 56 | String requestPath = URIUtil.newURI("http", "localhost", metricsServerPort, "/metrics", null); 57 | String responseText = RestAssured.when() 58 | .get(requestPath) 59 | .then() 60 | .statusCode(HttpStatus.OK_200) 61 | .contentType(TextFormat.CONTENT_TYPE_004) 62 | .extract() 63 | .asString(); 64 | 65 | String[] lines = responseText.split("\n"); 66 | assertEquals(lines[0], "# HELP bungeecord_online_players_total This is a mock metric of online bungeecord players"); 67 | assertEquals(lines[1], "# TYPE bungeecord_online_players_total counter"); 68 | assertEquals(lines[2], "bungeecord_online_players_total 233.0"); 69 | } 70 | 71 | private void mockPrometheusCounter(String name, String help, int value) { 72 | Counter mockPrometheusCounter = Counter.build().name(name).help(help).register(); 73 | mockPrometheusCounter.inc(value); 74 | } 75 | 76 | @Test 77 | void metrics_server_should_return_404_on_unknown_paths() { 78 | String requestPath = URIUtil.newURI("http", "localhost", metricsServerPort, "/unknown-path", null); 79 | 80 | RestAssured.when() 81 | .get(requestPath) 82 | .then() 83 | .statusCode(HttpStatus.NOT_FOUND_404); 84 | } 85 | 86 | } --------------------------------------------------------------------------------