├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── logo.png ├── minecraft-grafana.json ├── settings.gradle └── src └── main ├── java └── org │ └── stonebound │ └── prometheusexporter │ ├── JettyNullLogger.java │ ├── MetricsController.java │ └── PrometheusExporter.java └── resources └── jetty-logging.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | /.idea/ 3 | /target/ 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minecraft Prometheus Exporter 2 | 3 | A **Sponge plugin** which exports Minecraft server stats for Prometheus. 4 | 5 | ## Quick Start 6 | 7 | Drop the the plugin jar into your servers mods directory and start your server. 8 | 9 | After startup, the Prometheus metrics endpoint should be available at ``localhost:9225/metrics`` (assuming localhost is the server hostname). 10 | 11 | The metrics port can be customized in the plugin's config file (a default config will be created after the first use). 12 | 13 | ## Prometheus config 14 | 15 | Add the following job to the ``scrape_configs`` section of your Prometheus configuration: 16 | 17 | ### Single Server 18 | ```yml 19 | - job_name: 'minecraft' 20 | static_configs: 21 | - targets: ['localhost:9225'] 22 | ``` 23 | 24 | ### Multiple Server 25 | ```yml 26 | - job_name: 'minecraft' 27 | static_configs: 28 | - targets: ['localhost:9225'] 29 | labels: 30 | group: 'server1' 31 | - targets: ['localhost:9226'] 32 | labels: 33 | group: 'server2' 34 | - targets: ['localhost:9226'] 35 | labels: 36 | group: 'server3' 37 | ``` 38 | 39 | In the grafana json add `{group="serverX"}` to each search query. 40 | 41 | ## Import Grafana Dashboard 42 | 43 | 1. Navigate to Grafana -> Dashboards -> Import 44 | 1. Paste in or upload minecraft-grafana.json 45 | 1. Update "JVM Memory Used" to reflect your server max memory (Default 8G) 46 | 1. Edit (bottom right widget) -> Options -> Gauage -> Max 47 | 48 | ## Available metrics 49 | 50 | These are the stats that are currently exported by the plugin. 51 | 52 | Label | Description 53 | ------------ | ------------- 54 | mc_players_total | Online and Max Online players 55 | mc_tps | Overall tps 56 | mc_loaded_chunks_total | Chunks loaded per world 57 | mc_players_online_total | Online players per world 58 | mc_entities_total | Entities loaded per world 59 | mc_tile_entities_total | Tile Entities loaded per world 60 | mc_jvm_memory | JVM memory usage 61 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | mavenCentral() 5 | maven { 6 | name = 'sponge' 7 | url = 'http://repo.spongepowered.org/maven' 8 | } 9 | maven { 10 | name = 'forge' 11 | url = 'http://files.minecraftforge.net/maven' 12 | } 13 | } 14 | 15 | dependencies { 16 | classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' 17 | } 18 | } 19 | 20 | plugins { 21 | id 'signing' 22 | id 'com.github.johnrengelman.shadow' version '1.2.4' 23 | id 'org.spongepowered.plugin' version '0.8.1' 24 | id 'net.minecrell.vanillagradle.server' version '2.2-6' 25 | } 26 | 27 | minecraft { 28 | mappings = 'snapshot_20171007' 29 | version = '1.12.2' 30 | } 31 | 32 | group = 'org.stonebound.prometheusexporter' 33 | version = '1.3.2' 34 | description = "Prometheus Exporter for Sponge" 35 | 36 | compileJava { 37 | sourceCompatibility = '1.8' 38 | targetCompatibility = '1.8' 39 | } 40 | 41 | defaultTasks 'build', 'shadowJar' 42 | 43 | dependencies { 44 | compile 'org.spongepowered:spongeapi:7.0.0' 45 | compile 'io.prometheus:simpleclient_common:0.0.26' 46 | compile 'org.eclipse.jetty:jetty-server:9.4.6.v20170531' 47 | compile 'javax.servlet:javax.servlet-api:4.0.0' 48 | } 49 | 50 | shadowJar { 51 | relocate 'org.eclipse.jetty', 'prometheus.shadow.jetty' 52 | relocate 'io.prometheus', 'prometheus.shadow.prometheus' 53 | relocate 'javax.servlet', 'prometheus.shadow.servlet' 54 | 55 | dependencies { 56 | include dependency('io.prometheus:simpleclient_common:0.0.26') 57 | include dependency('io.prometheus:simpleclient:0.0.26') 58 | include dependency('org.eclipse.jetty:jetty-server:9.4.6.v20170531') 59 | include dependency('org.eclipse.jetty:jetty-http:9.4.6.v20170531') 60 | include dependency('org.eclipse.jetty:jetty-io:9.4.6.v20170531') 61 | include dependency('org.eclipse.jetty:jetty-util:9.4.6.v20170531') 62 | include dependency('javax.servlet:javax.servlet-api:4.0.0') 63 | } 64 | 65 | exclude 'jetty-dir.css' 66 | exclude 'about.html' 67 | 68 | archiveName = "spongeprometheusexporter-${version}-plugin.jar" 69 | } 70 | 71 | reobf { 72 | shadowJar{} 73 | } 74 | 75 | signing { 76 | if (project.hasProperty('signing.keyId') && project.hasProperty('signing.password') && project.hasProperty('signing.secretKeyRingFile')) { 77 | sign configurations.shadow 78 | } 79 | } 80 | 81 | tasks.signShadow.dependsOn retromapReplacedMain -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stonebound/PrometheusExporter/d7b5d2aef80258e1cb0620ecb8232b8c469510a1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 01 16:31:33 CET 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stonebound/PrometheusExporter/d7b5d2aef80258e1cb0620ecb8232b8c469510a1/logo.png -------------------------------------------------------------------------------- /minecraft-grafana.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [], 3 | "__requires": [ 4 | { 5 | "type": "grafana", 6 | "id": "grafana", 7 | "name": "Grafana", 8 | "version": "6.1.3" 9 | }, 10 | { 11 | "type": "panel", 12 | "id": "graph", 13 | "name": "Graph", 14 | "version": "" 15 | }, 16 | { 17 | "type": "panel", 18 | "id": "singlestat", 19 | "name": "Singlestat", 20 | "version": "" 21 | } 22 | ], 23 | "annotations": { 24 | "list": [ 25 | { 26 | "builtIn": 1, 27 | "datasource": "-- Grafana --", 28 | "enable": true, 29 | "hide": true, 30 | "iconColor": "rgba(0, 211, 255, 1)", 31 | "name": "Annotations & Alerts", 32 | "type": "dashboard" 33 | } 34 | ] 35 | }, 36 | "editable": true, 37 | "gnetId": null, 38 | "graphTooltip": 1, 39 | "id": null, 40 | "links": [ 41 | { 42 | "asDropdown": false, 43 | "icon": "external link", 44 | "tags": [ 45 | "server" 46 | ], 47 | "type": "dashboards" 48 | } 49 | ], 50 | "panels": [ 51 | { 52 | "aliasColors": {}, 53 | "bars": false, 54 | "dashLength": 10, 55 | "dashes": false, 56 | "fill": 1, 57 | "gridPos": { 58 | "h": 7, 59 | "w": 10, 60 | "x": 0, 61 | "y": 0 62 | }, 63 | "id": 1, 64 | "legend": { 65 | "avg": false, 66 | "current": false, 67 | "max": false, 68 | "min": false, 69 | "show": true, 70 | "total": false, 71 | "values": false 72 | }, 73 | "lines": true, 74 | "linewidth": 1, 75 | "links": [], 76 | "nullPointMode": "null", 77 | "paceLength": 10, 78 | "percentage": false, 79 | "pointradius": 5, 80 | "points": false, 81 | "renderer": "flot", 82 | "seriesOverrides": [], 83 | "spaceLength": 10, 84 | "stack": false, 85 | "steppedLine": true, 86 | "targets": [ 87 | { 88 | "expr": "mc_players_total{group=\"groupidforserver\"}", 89 | "format": "time_series", 90 | "intervalFactor": 2, 91 | "legendFormat": "{{state}}", 92 | "metric": "mc_players_total", 93 | "refId": "A", 94 | "step": 240 95 | } 96 | ], 97 | "thresholds": [], 98 | "timeFrom": null, 99 | "timeRegions": [], 100 | "timeShift": null, 101 | "title": "Players", 102 | "tooltip": { 103 | "shared": true, 104 | "sort": 0, 105 | "value_type": "individual" 106 | }, 107 | "type": "graph", 108 | "xaxis": { 109 | "buckets": null, 110 | "mode": "time", 111 | "name": null, 112 | "show": true, 113 | "values": [] 114 | }, 115 | "yaxes": [ 116 | { 117 | "format": "short", 118 | "label": null, 119 | "logBase": 1, 120 | "max": null, 121 | "min": null, 122 | "show": true 123 | }, 124 | { 125 | "format": "short", 126 | "label": null, 127 | "logBase": 1, 128 | "max": null, 129 | "min": null, 130 | "show": true 131 | } 132 | ], 133 | "yaxis": { 134 | "align": false, 135 | "alignLevel": null 136 | } 137 | }, 138 | { 139 | "cacheTimeout": null, 140 | "colorBackground": false, 141 | "colorValue": true, 142 | "colors": [ 143 | "rgba(245, 54, 54, 0.9)", 144 | "rgba(237, 129, 40, 0.89)", 145 | "rgba(50, 172, 45, 0.97)" 146 | ], 147 | "format": "none", 148 | "gauge": { 149 | "maxValue": 100, 150 | "minValue": 0, 151 | "show": false, 152 | "thresholdLabels": false, 153 | "thresholdMarkers": true 154 | }, 155 | "gridPos": { 156 | "h": 7, 157 | "w": 2, 158 | "x": 10, 159 | "y": 0 160 | }, 161 | "id": 2, 162 | "interval": null, 163 | "links": [], 164 | "mappingType": 1, 165 | "mappingTypes": [ 166 | { 167 | "name": "value to text", 168 | "value": 1 169 | }, 170 | { 171 | "name": "range to text", 172 | "value": 2 173 | } 174 | ], 175 | "maxDataPoints": 100, 176 | "nullPointMode": "connected", 177 | "nullText": null, 178 | "postfix": "", 179 | "postfixFontSize": "50%", 180 | "prefix": "", 181 | "prefixFontSize": "50%", 182 | "rangeMaps": [ 183 | { 184 | "from": "null", 185 | "text": "N/A", 186 | "to": "null" 187 | } 188 | ], 189 | "sparkline": { 190 | "fillColor": "rgba(31, 189, 42, 0.18)", 191 | "full": false, 192 | "lineColor": "rgb(31, 120, 193)", 193 | "show": false 194 | }, 195 | "tableColumn": "", 196 | "targets": [ 197 | { 198 | "expr": "mc_players_total{state=\"online\",group=\"groupidforserver\"}", 199 | "format": "time_series", 200 | "interval": "", 201 | "intervalFactor": 2, 202 | "legendFormat": "", 203 | "metric": "mc_players_total", 204 | "refId": "A", 205 | "step": 1800 206 | } 207 | ], 208 | "thresholds": "0,1", 209 | "title": "Players Online", 210 | "type": "singlestat", 211 | "valueFontSize": "200%", 212 | "valueMaps": [ 213 | { 214 | "op": "=", 215 | "text": "N/A", 216 | "value": "null" 217 | } 218 | ], 219 | "valueName": "current" 220 | }, 221 | { 222 | "aliasColors": {}, 223 | "bars": false, 224 | "dashLength": 10, 225 | "dashes": false, 226 | "fill": 1, 227 | "gridPos": { 228 | "h": 7, 229 | "w": 8, 230 | "x": 12, 231 | "y": 0 232 | }, 233 | "id": 7, 234 | "legend": { 235 | "avg": false, 236 | "current": false, 237 | "max": false, 238 | "min": false, 239 | "show": true, 240 | "total": false, 241 | "values": false 242 | }, 243 | "lines": true, 244 | "linewidth": 1, 245 | "links": [], 246 | "nullPointMode": "null", 247 | "paceLength": 10, 248 | "percentage": false, 249 | "pointradius": 5, 250 | "points": false, 251 | "renderer": "flot", 252 | "seriesOverrides": [], 253 | "spaceLength": 10, 254 | "stack": false, 255 | "steppedLine": false, 256 | "targets": [ 257 | { 258 | "expr": "mc_jvm_memory{group=\"groupidforserver\",type=\"max\"}", 259 | "format": "time_series", 260 | "intervalFactor": 2, 261 | "legendFormat": "mem{{type}}", 262 | "metric": "mc_jvm_memory", 263 | "refId": "A", 264 | "step": 240 265 | }, 266 | { 267 | "expr": "mc_jvm_memory{group=\"groupidforserver\",type=\"used\"}", 268 | "format": "time_series", 269 | "intervalFactor": 2, 270 | "legendFormat": "mem{{type}}", 271 | "refId": "B", 272 | "step": 240 273 | } 274 | ], 275 | "thresholds": [], 276 | "timeFrom": null, 277 | "timeRegions": [], 278 | "timeShift": null, 279 | "title": "JVM Memory", 280 | "tooltip": { 281 | "shared": true, 282 | "sort": 0, 283 | "value_type": "individual" 284 | }, 285 | "type": "graph", 286 | "xaxis": { 287 | "buckets": null, 288 | "mode": "time", 289 | "name": null, 290 | "show": true, 291 | "values": [] 292 | }, 293 | "yaxes": [ 294 | { 295 | "format": "bytes", 296 | "label": null, 297 | "logBase": 1, 298 | "max": null, 299 | "min": null, 300 | "show": true 301 | }, 302 | { 303 | "format": "short", 304 | "label": null, 305 | "logBase": 1, 306 | "max": null, 307 | "min": null, 308 | "show": true 309 | } 310 | ], 311 | "yaxis": { 312 | "align": false, 313 | "alignLevel": null 314 | } 315 | }, 316 | { 317 | "cacheTimeout": null, 318 | "colorBackground": false, 319 | "colorValue": false, 320 | "colors": [ 321 | "rgba(245, 54, 54, 0.9)", 322 | "rgba(237, 129, 40, 0.89)", 323 | "rgba(50, 172, 45, 0.97)" 324 | ], 325 | "decimals": null, 326 | "format": "bytes", 327 | "gauge": { 328 | "maxValue": 6000000000, 329 | "minValue": 0, 330 | "show": true, 331 | "thresholdLabels": false, 332 | "thresholdMarkers": true 333 | }, 334 | "gridPos": { 335 | "h": 7, 336 | "w": 4, 337 | "x": 20, 338 | "y": 0 339 | }, 340 | "id": 8, 341 | "interval": null, 342 | "links": [], 343 | "mappingType": 1, 344 | "mappingTypes": [ 345 | { 346 | "name": "value to text", 347 | "value": 1 348 | }, 349 | { 350 | "name": "range to text", 351 | "value": 2 352 | } 353 | ], 354 | "maxDataPoints": 100, 355 | "nullPointMode": "connected", 356 | "nullText": null, 357 | "postfix": "", 358 | "postfixFontSize": "50%", 359 | "prefix": "", 360 | "prefixFontSize": "50%", 361 | "rangeMaps": [ 362 | { 363 | "from": "null", 364 | "text": "N/A", 365 | "to": "null" 366 | } 367 | ], 368 | "sparkline": { 369 | "fillColor": "rgba(31, 118, 189, 0.18)", 370 | "full": false, 371 | "lineColor": "rgb(31, 120, 193)", 372 | "show": false 373 | }, 374 | "tableColumn": "", 375 | "targets": [ 376 | { 377 | "expr": "mc_jvm_memory{type=\"free\",group=\"groupidforserver\"}", 378 | "format": "time_series", 379 | "intervalFactor": 2, 380 | "legendFormat": "", 381 | "metric": "mc_jvm_memory", 382 | "refId": "A", 383 | "step": 1800 384 | } 385 | ], 386 | "thresholds": "500000000", 387 | "title": "JVM Memory Free", 388 | "type": "singlestat", 389 | "valueFontSize": "80%", 390 | "valueMaps": [ 391 | { 392 | "op": "=", 393 | "text": "N/A", 394 | "value": "null" 395 | } 396 | ], 397 | "valueName": "current" 398 | }, 399 | { 400 | "aliasColors": {}, 401 | "bars": false, 402 | "dashLength": 10, 403 | "dashes": false, 404 | "fill": 1, 405 | "gridPos": { 406 | "h": 7, 407 | "w": 24, 408 | "x": 0, 409 | "y": 7 410 | }, 411 | "id": 9, 412 | "legend": { 413 | "avg": true, 414 | "current": false, 415 | "max": false, 416 | "min": true, 417 | "show": true, 418 | "total": false, 419 | "values": true 420 | }, 421 | "lines": true, 422 | "linewidth": 1, 423 | "links": [], 424 | "nullPointMode": "null", 425 | "paceLength": 10, 426 | "percentage": false, 427 | "pointradius": 5, 428 | "points": false, 429 | "renderer": "flot", 430 | "seriesOverrides": [], 431 | "spaceLength": 10, 432 | "stack": false, 433 | "steppedLine": true, 434 | "targets": [ 435 | { 436 | "expr": "mc_tps{state=\"tps\",group=\"groupidforserver\"}", 437 | "format": "time_series", 438 | "intervalFactor": 2, 439 | "legendFormat": "tps", 440 | "metric": "mc_tps", 441 | "refId": "A", 442 | "step": 120 443 | } 444 | ], 445 | "thresholds": [], 446 | "timeFrom": null, 447 | "timeRegions": [], 448 | "timeShift": null, 449 | "title": "TPS", 450 | "tooltip": { 451 | "shared": true, 452 | "sort": 0, 453 | "value_type": "individual" 454 | }, 455 | "type": "graph", 456 | "xaxis": { 457 | "buckets": null, 458 | "mode": "time", 459 | "name": null, 460 | "show": true, 461 | "values": [] 462 | }, 463 | "yaxes": [ 464 | { 465 | "format": "short", 466 | "label": null, 467 | "logBase": 1, 468 | "max": "20", 469 | "min": "0", 470 | "show": true 471 | }, 472 | { 473 | "format": "short", 474 | "label": null, 475 | "logBase": 1, 476 | "max": null, 477 | "min": null, 478 | "show": true 479 | } 480 | ], 481 | "yaxis": { 482 | "align": false, 483 | "alignLevel": null 484 | } 485 | }, 486 | { 487 | "aliasColors": {}, 488 | "bars": false, 489 | "dashLength": 10, 490 | "dashes": false, 491 | "fill": 1, 492 | "gridPos": { 493 | "h": 7, 494 | "w": 12, 495 | "x": 0, 496 | "y": 14 497 | }, 498 | "id": 3, 499 | "legend": { 500 | "avg": true, 501 | "current": false, 502 | "max": false, 503 | "min": false, 504 | "show": true, 505 | "total": false, 506 | "values": true 507 | }, 508 | "lines": true, 509 | "linewidth": 1, 510 | "links": [], 511 | "nullPointMode": "null", 512 | "paceLength": 10, 513 | "percentage": false, 514 | "pointradius": 5, 515 | "points": false, 516 | "renderer": "flot", 517 | "seriesOverrides": [], 518 | "spaceLength": 10, 519 | "stack": false, 520 | "steppedLine": true, 521 | "targets": [ 522 | { 523 | "expr": "mc_loaded_chunks{group=\"groupidforserver\"}", 524 | "format": "time_series", 525 | "intervalFactor": 2, 526 | "legendFormat": "{{world}}", 527 | "metric": "mc_loaded_chunks_total", 528 | "refId": "A", 529 | "step": 240 530 | } 531 | ], 532 | "thresholds": [], 533 | "timeFrom": null, 534 | "timeRegions": [], 535 | "timeShift": null, 536 | "title": "Loaded Chunks", 537 | "tooltip": { 538 | "shared": true, 539 | "sort": 0, 540 | "value_type": "individual" 541 | }, 542 | "type": "graph", 543 | "xaxis": { 544 | "buckets": null, 545 | "mode": "time", 546 | "name": null, 547 | "show": true, 548 | "values": [] 549 | }, 550 | "yaxes": [ 551 | { 552 | "format": "short", 553 | "label": null, 554 | "logBase": 1, 555 | "max": null, 556 | "min": null, 557 | "show": true 558 | }, 559 | { 560 | "format": "short", 561 | "label": null, 562 | "logBase": 1, 563 | "max": null, 564 | "min": null, 565 | "show": true 566 | } 567 | ], 568 | "yaxis": { 569 | "align": false, 570 | "alignLevel": null 571 | } 572 | }, 573 | { 574 | "aliasColors": {}, 575 | "bars": false, 576 | "dashLength": 10, 577 | "dashes": false, 578 | "fill": 1, 579 | "gridPos": { 580 | "h": 7, 581 | "w": 12, 582 | "x": 12, 583 | "y": 14 584 | }, 585 | "id": 11, 586 | "legend": { 587 | "avg": true, 588 | "current": false, 589 | "max": false, 590 | "min": false, 591 | "show": true, 592 | "total": false, 593 | "values": true 594 | }, 595 | "lines": true, 596 | "linewidth": 1, 597 | "links": [], 598 | "nullPointMode": "null", 599 | "paceLength": 10, 600 | "percentage": false, 601 | "pointradius": 5, 602 | "points": false, 603 | "renderer": "flot", 604 | "seriesOverrides": [], 605 | "spaceLength": 10, 606 | "stack": false, 607 | "steppedLine": true, 608 | "targets": [ 609 | { 610 | "expr": "mc_tps{state=\"meanticktime\",group=\"groupidforserver\"}", 611 | "format": "time_series", 612 | "intervalFactor": 2, 613 | "legendFormat": "time per tick", 614 | "refId": "A", 615 | "step": 240 616 | } 617 | ], 618 | "thresholds": [ 619 | { 620 | "colorMode": "warning", 621 | "fill": false, 622 | "line": true, 623 | "op": "gt", 624 | "value": 50 625 | } 626 | ], 627 | "timeFrom": null, 628 | "timeRegions": [], 629 | "timeShift": null, 630 | "title": "Mean Tick Time", 631 | "tooltip": { 632 | "shared": true, 633 | "sort": 0, 634 | "value_type": "individual" 635 | }, 636 | "type": "graph", 637 | "xaxis": { 638 | "buckets": null, 639 | "mode": "time", 640 | "name": null, 641 | "show": true, 642 | "values": [] 643 | }, 644 | "yaxes": [ 645 | { 646 | "format": "ms", 647 | "label": null, 648 | "logBase": 1, 649 | "max": null, 650 | "min": null, 651 | "show": true 652 | }, 653 | { 654 | "format": "short", 655 | "label": null, 656 | "logBase": 1, 657 | "max": null, 658 | "min": null, 659 | "show": true 660 | } 661 | ], 662 | "yaxis": { 663 | "align": false, 664 | "alignLevel": null 665 | } 666 | }, 667 | { 668 | "aliasColors": {}, 669 | "bars": false, 670 | "dashLength": 10, 671 | "dashes": false, 672 | "fill": 1, 673 | "gridPos": { 674 | "h": 7, 675 | "w": 12, 676 | "x": 0, 677 | "y": 21 678 | }, 679 | "id": 4, 680 | "legend": { 681 | "avg": true, 682 | "current": false, 683 | "max": false, 684 | "min": false, 685 | "show": true, 686 | "total": false, 687 | "values": true 688 | }, 689 | "lines": true, 690 | "linewidth": 1, 691 | "links": [], 692 | "nullPointMode": "null", 693 | "paceLength": 10, 694 | "percentage": false, 695 | "pointradius": 5, 696 | "points": false, 697 | "renderer": "flot", 698 | "seriesOverrides": [], 699 | "spaceLength": 10, 700 | "stack": false, 701 | "steppedLine": true, 702 | "targets": [ 703 | { 704 | "expr": "mc_entities{group=\"groupidforserver\"}", 705 | "format": "time_series", 706 | "intervalFactor": 2, 707 | "legendFormat": "{{world}}_total", 708 | "metric": "mc_entities_total", 709 | "refId": "A", 710 | "step": 240 711 | } 712 | ], 713 | "thresholds": [], 714 | "timeFrom": null, 715 | "timeRegions": [], 716 | "timeShift": null, 717 | "title": "Entities", 718 | "tooltip": { 719 | "shared": true, 720 | "sort": 0, 721 | "value_type": "individual" 722 | }, 723 | "type": "graph", 724 | "xaxis": { 725 | "buckets": null, 726 | "mode": "time", 727 | "name": null, 728 | "show": true, 729 | "values": [] 730 | }, 731 | "yaxes": [ 732 | { 733 | "format": "short", 734 | "label": null, 735 | "logBase": 1, 736 | "max": null, 737 | "min": null, 738 | "show": true 739 | }, 740 | { 741 | "format": "short", 742 | "label": null, 743 | "logBase": 1, 744 | "max": null, 745 | "min": null, 746 | "show": true 747 | } 748 | ], 749 | "yaxis": { 750 | "align": false, 751 | "alignLevel": null 752 | } 753 | }, 754 | { 755 | "aliasColors": {}, 756 | "bars": false, 757 | "dashLength": 10, 758 | "dashes": false, 759 | "fill": 1, 760 | "gridPos": { 761 | "h": 7, 762 | "w": 12, 763 | "x": 12, 764 | "y": 21 765 | }, 766 | "id": 10, 767 | "legend": { 768 | "avg": true, 769 | "current": false, 770 | "max": false, 771 | "min": false, 772 | "show": true, 773 | "total": false, 774 | "values": true 775 | }, 776 | "lines": true, 777 | "linewidth": 1, 778 | "links": [], 779 | "nullPointMode": "null", 780 | "paceLength": 10, 781 | "percentage": false, 782 | "pointradius": 5, 783 | "points": false, 784 | "renderer": "flot", 785 | "seriesOverrides": [], 786 | "spaceLength": 10, 787 | "stack": false, 788 | "steppedLine": true, 789 | "targets": [ 790 | { 791 | "expr": "mc_tile_entities{group=\"groupidforserver\"}", 792 | "format": "time_series", 793 | "intervalFactor": 2, 794 | "legendFormat": "{{world}}_total", 795 | "metric": "mc_entities_total", 796 | "refId": "A", 797 | "step": 240 798 | } 799 | ], 800 | "thresholds": [], 801 | "timeFrom": null, 802 | "timeRegions": [], 803 | "timeShift": null, 804 | "title": "Tile Entities", 805 | "tooltip": { 806 | "shared": true, 807 | "sort": 0, 808 | "value_type": "individual" 809 | }, 810 | "type": "graph", 811 | "xaxis": { 812 | "buckets": null, 813 | "mode": "time", 814 | "name": null, 815 | "show": true, 816 | "values": [] 817 | }, 818 | "yaxes": [ 819 | { 820 | "format": "short", 821 | "label": null, 822 | "logBase": 1, 823 | "max": null, 824 | "min": null, 825 | "show": true 826 | }, 827 | { 828 | "format": "short", 829 | "label": null, 830 | "logBase": 1, 831 | "max": null, 832 | "min": null, 833 | "show": true 834 | } 835 | ], 836 | "yaxis": { 837 | "align": false, 838 | "alignLevel": null 839 | } 840 | } 841 | ], 842 | "refresh": "30s", 843 | "schemaVersion": 18, 844 | "style": "dark", 845 | "tags": [ 846 | "server" 847 | ], 848 | "templating": { 849 | "list": [] 850 | }, 851 | "time": { 852 | "from": "now-24h", 853 | "to": "now" 854 | }, 855 | "timepicker": { 856 | "refresh_intervals": [ 857 | "5s", 858 | "10s", 859 | "30s", 860 | "1m", 861 | "5m", 862 | "15m", 863 | "30m", 864 | "1h", 865 | "2h", 866 | "1d" 867 | ], 868 | "time_options": [ 869 | "5m", 870 | "15m", 871 | "1h", 872 | "6h", 873 | "12h", 874 | "24h", 875 | "2d", 876 | "7d", 877 | "30d" 878 | ] 879 | }, 880 | "timezone": "browser", 881 | "title": "Server Title", 882 | "uid": "uniqueurl", 883 | "version": 5 884 | } 885 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'SpongePrometheusExporter' 2 | -------------------------------------------------------------------------------- /src/main/java/org/stonebound/prometheusexporter/JettyNullLogger.java: -------------------------------------------------------------------------------- 1 | package org.stonebound.prometheusexporter; 2 | 3 | import org.eclipse.jetty.util.log.Logger; 4 | 5 | public class JettyNullLogger implements Logger { 6 | @Override public String getName() { return "Jetty"; } 7 | @Override public void warn(String msg, Object... args) { } 8 | @Override public void warn(Throwable thrown) { } 9 | @Override public void warn(String msg, Throwable thrown) { } 10 | @Override public void info(String msg, Object... args) { } 11 | @Override public void info(Throwable thrown) { } 12 | @Override public void info(String msg, Throwable thrown) { } 13 | @Override public boolean isDebugEnabled() { return false; } 14 | @Override public void setDebugEnabled(boolean enabled) { } 15 | @Override public void debug(String msg, Object... args) { } 16 | @Override public void debug(String msg, long value) { } 17 | @Override public void debug(Throwable thrown) { } 18 | @Override public void debug(String msg, Throwable thrown) { } 19 | @Override public Logger getLogger(String name) { return this; } 20 | @Override public void ignore(Throwable ignored) { } 21 | } -------------------------------------------------------------------------------- /src/main/java/org/stonebound/prometheusexporter/MetricsController.java: -------------------------------------------------------------------------------- 1 | package org.stonebound.prometheusexporter; 2 | 3 | import com.google.common.collect.Iterables; 4 | import io.prometheus.client.CollectorRegistry; 5 | import io.prometheus.client.Gauge; 6 | import io.prometheus.client.exporter.common.TextFormat; 7 | import net.minecraft.server.MinecraftServer; 8 | import org.eclipse.jetty.server.Request; 9 | import org.eclipse.jetty.server.handler.AbstractHandler; 10 | import org.spongepowered.api.Sponge; 11 | import org.spongepowered.api.world.World; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | public class MetricsController extends AbstractHandler { 18 | private final PrometheusExporter exporter; 19 | 20 | private Gauge players = Gauge.build().name("mc_players_total").help("Total online and max players").labelNames("state").create().register(); 21 | private Gauge tps = Gauge.build().name("mc_tps").help("Tickrate").labelNames("state").create().register(); 22 | private Gauge loadedChunks = Gauge.build().name("mc_loaded_chunks").help("Chunks loaded per world").labelNames("world").create().register(); 23 | private Gauge playersOnline = Gauge.build().name("mc_players_online").help("Players currently online per world").labelNames("world").create().register(); 24 | private Gauge entities = Gauge.build().name("mc_entities").help("Entities loaded per world").labelNames("world").create().register(); 25 | private Gauge tileEntities = Gauge.build().name("mc_tile_entities").help("Entities loaded per world").labelNames("world").create().register(); 26 | private Gauge memory = Gauge.build().name("mc_jvm_memory").help("JVM memory usage").labelNames("type").create().register(); 27 | 28 | public MetricsController(PrometheusExporter exporter) { 29 | this.exporter = exporter; 30 | } 31 | 32 | public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { 33 | if (!target.equals("/metrics")) { 34 | response.sendError(HttpServletResponse.SC_NOT_FOUND); 35 | return; 36 | } 37 | 38 | players.labels("online").set(Sponge.getGame().getServer().getOnlinePlayers().size()); 39 | players.labels("max").set(Sponge.getGame().getServer().getMaxPlayers()); 40 | 41 | for (World world : Sponge.getGame().getServer().getWorlds()) { 42 | loadedChunks.labels(world.getName()).set(Iterables.size(world.getLoadedChunks())); 43 | playersOnline.labels(world.getName()).set(world.getPlayers().size()); 44 | entities.labels(world.getName()).set(world.getEntities().size()); 45 | tileEntities.labels(world.getName()).set(world.getTileEntities().size()); 46 | } 47 | double meanTickTime = MetricsController.mean(((MinecraftServer) Sponge.getServer()).tickTimeArray) * 1.0E-6D; 48 | 49 | tps.labels("tps").set(Sponge.getGame().getServer().getTicksPerSecond()); 50 | tps.labels("meanticktime").set(meanTickTime); 51 | memory.labels("max").set(Runtime.getRuntime().maxMemory()); 52 | memory.labels("free").set(Runtime.getRuntime().freeMemory()); 53 | memory.labels("used").set(Runtime.getRuntime().maxMemory() - Runtime.getRuntime().freeMemory()); 54 | 55 | try { 56 | response.setStatus(HttpServletResponse.SC_OK); 57 | response.setContentType(TextFormat.CONTENT_TYPE_004); 58 | 59 | TextFormat.write004(response.getWriter(), CollectorRegistry.defaultRegistry.metricFamilySamples()); 60 | 61 | baseRequest.setHandled(true); 62 | } catch (IOException e) { 63 | exporter.getLogger().error("Failed to read server statistics", e); 64 | response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 65 | } 66 | } 67 | 68 | private static long mean(long[] values) 69 | { 70 | long sum = 0l; 71 | for (long v : values) 72 | { 73 | sum+=v; 74 | } 75 | 76 | return sum / values.length; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/stonebound/prometheusexporter/PrometheusExporter.java: -------------------------------------------------------------------------------- 1 | package org.stonebound.prometheusexporter; 2 | 3 | import com.google.inject.Inject; 4 | import ninja.leaping.configurate.ConfigurationNode; 5 | import ninja.leaping.configurate.commented.CommentedConfigurationNode; 6 | import ninja.leaping.configurate.loader.ConfigurationLoader; 7 | import org.eclipse.jetty.server.Server; 8 | import org.slf4j.Logger; 9 | import org.spongepowered.api.config.DefaultConfig; 10 | import org.spongepowered.api.event.Listener; 11 | import org.spongepowered.api.event.game.state.GamePreInitializationEvent; 12 | import org.spongepowered.api.event.game.state.GameStartedServerEvent; 13 | import org.spongepowered.api.event.game.state.GameStoppingServerEvent; 14 | import org.spongepowered.api.plugin.Plugin; 15 | 16 | import java.io.File; 17 | 18 | @Plugin(id = "spongeprometheusexporter", 19 | name = "SpongePrometheusExporter", 20 | version = "1.3.2", 21 | description = "Prometheus Exporter for Sponge", 22 | url = "https://ore.spongepowered.org/phit/PrometheusExporter", 23 | authors = "phit" 24 | ) 25 | 26 | public class PrometheusExporter { 27 | @Inject 28 | private Server server; 29 | private int port; 30 | 31 | @Inject 32 | @DefaultConfig(sharedRoot = false) 33 | private File config; 34 | 35 | @Inject 36 | @DefaultConfig(sharedRoot = false) 37 | private ConfigurationLoader cfgMgr; 38 | private ConfigurationNode cfg; 39 | 40 | @Inject 41 | private Logger logger; 42 | public Logger getLogger() { 43 | return this.logger; 44 | } 45 | 46 | @Listener 47 | public void onPreinit(GamePreInitializationEvent event) { 48 | logger.info("Setting up config..."); 49 | try { 50 | if (!config.exists()) { 51 | config.createNewFile(); 52 | 53 | this.cfg = cfgMgr.load(); 54 | this.cfg.getNode("exporter", "port").setValue(9225); 55 | this.cfgMgr.save(cfg); 56 | } 57 | 58 | this.cfg = cfgMgr.load(); 59 | port = cfg.getNode("exporter", "port").getInt(); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | @Listener 66 | public void onServerStarted(GameStartedServerEvent event) { 67 | try { 68 | server = new Server(port); 69 | 70 | server.setHandler(new MetricsController(this)); 71 | server.start(); 72 | 73 | logger.info("Started Prometheus metrics endpoint on port " + port); 74 | 75 | } catch (Exception e) { 76 | logger.error("Could not start embedded Jetty server", e); 77 | } 78 | } 79 | 80 | @Listener 81 | public void onServerStop(GameStoppingServerEvent event) { 82 | if (server != null) { 83 | try { 84 | server.stop(); 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/resources/jetty-logging.properties: -------------------------------------------------------------------------------- 1 | prometheus.shadow.jetty.util.log.class=org.stonebound.prometheusexporter.JettyNullLogger --------------------------------------------------------------------------------