@cryptotaxi247 / netdata-1 / commits / 4b0a7d8c4

Add a plugin for Spigot Minecraft server stats.

THis adds a plugin which tracks rudimentary stats from a Spigot Minecraft server using the remote console protocol. It provides charts for the number of currently connected users, as well as the number of ticks the server is processing each second (which correlates strongly with server-side lag). The plugin makes use of a Python implementation of the remote console protocol originally written by Barnaby Gale and released under an MIT license. This bundles the module with netdata as it's tiny (less than 100 lines of code) and is not packaged in any distributions I know of. The default settings are to connect to the local system on the standard remote console port (25575) without a password, which allows for trivial setup (users just need to set `enable-rcon` to true in the server.properties file for their Spigot server). Currently, this plugin uses the SimpleService framework instead of the SocketService plugin, which actually makes implementation marginally simpler. In theory, it could probably be re-implemented using the SocketService, but I very much doubt it would be any more efficient than it is now (we already keep the socket open and connected persistently so we don't have to re-authenticate, and we make the protocol synchronous so it's trivial to determine that we've recieved everything because a read returns no new data).

Austin S. Hemmelgarn committed Apr 27, 2018 at 14:21 UTC 4b0a7d8c496c074f7342343c31a822a01213c2f5
8 files changed +282
LICENSE-REDISTRIBUTED.md
+7
@@ -192,3 +192,10 @@ connectivity is not available.
192
193 Copyright (c) 2010-2015 Benjamin Peterson
194 [MIT License](https://github.com/firehol/netdata/blob/master/python.d/python_modules/urllib3/packages/six.py)
195 +
196 +
197 +- [mcrcon](https://github.com/barneygale/MCRcon)
198 +
199 + Copyright (C) 2015 Barnaby Gale
200 + [MIT License](https://raw.githubusercontent.com/barneygale/MCRcon/master/COPYING.txt)
201 +
conf.d/Makefile.am
+1
@@ -68,6 +68,7 @@ dist_pythonconfig_DATA = \
68 python.d/samba.conf \
69 python.d/sensors.conf \
70 python.d/springboot.conf \
71 + python.d/spigotmc.conf \
72 python.d/squid.conf \
73 python.d/smartd_log.conf \
74 python.d/tomcat.conf \
conf.d/python.d/spigotmc.conf new
+68
@@ -0,0 +1,68 @@
1 +# netdata python.d.plugin configuration for spigotmc
2 +#
3 +# This file is in YaML format. Generally the format is:
4 +#
5 +# name: value
6 +#
7 +# There are 2 sections:
8 +# - global variables
9 +# - one or more JOBS
10 +#
11 +# JOBS allow you to collect values from multiple sources.
12 +# Each source will have its own set of charts.
13 +#
14 +# JOB parameters have to be indented (using spaces only, example below).
15 +
16 +# ----------------------------------------------------------------------
17 +# Global Variables
18 +# These variables set the defaults for all JOBs, however each JOB
19 +# may define its own, overriding the defaults.
20 +
21 +# update_every sets the default data collection frequency.
22 +# If unset, the python.d.plugin default is used.
23 +# update_every: 1
24 +
25 +# priority controls the order of charts at the netdata dashboard.
26 +# Lower numbers move the charts towards the top of the page.
27 +# If unset, the default for python.d.plugin is used.
28 +# priority: 60000
29 +
30 +# retries sets the number of retries to be made in case of failures.
31 +# If unset, the default for python.d.plugin is used.
32 +# Attempts to restore the service are made once every update_every
33 +# and only if the module has collected values in the past.
34 +# retries: 60
35 +
36 +# autodetection_retry sets the job re-check interval in seconds.
37 +# The job is not deleted if check fails.
38 +# Attempts to start the job are made once every autodetection_retry.
39 +# This feature is disabled by default.
40 +# autodetection_retry: 0
41 +
42 +# ----------------------------------------------------------------------
43 +# JOBS (data collection sources)
44 +#
45 +# The default JOBS share the same *name*. JOBS with the same name
46 +# are mutually exclusive. Only one of them will be allowed running at
47 +# any time. This allows autodetection to try several alternatives and
48 +# pick the one that works.
49 +#
50 +# Any number of jobs is supported.
51 +#
52 +# All python.d.plugin JOBS (for all its modules) support a set of
53 +# predefined parameters. These are:
54 +#
55 +# job_name:
56 +# name: myname # the JOB's name as it will appear at the
57 +# # dashboard (by default is the job_name)
58 +# # JOBs sharing a name are mutually exclusive
59 +# update_every: 1 # the JOB's data collection frequency
60 +# priority: 60000 # the JOB's order on the dashboard
61 +# retries: 60 # the JOB's number of restoration attempts
62 +# autodetection_retry: 0 # the JOB's re-check interval in seconds
63 +#
64 +# In addition to the above, spigotmc supports the following:
65 +#
66 +# host: localhost # The host to connect to. Defaults to the local system.
67 +# port: 25575 # THe port the remote console is listening on.
68 +# password: '' # The remote console password. Most be set correctly.
python.d/Makefile.am
+1
@@ -55,6 +55,7 @@ dist_python_DATA = \
55 retroshare.chart.py \
56 samba.chart.py \
57 sensors.chart.py \
58 + spigotmc.chart.py \
59 springboot.chart.py \
60 squid.chart.py \
61 smartd_log.chart.py \
python.d/README.md
+23
@@ -2027,6 +2027,29 @@ Please join this discussion for help.
2027
2028 ---
2029
2030 +# spigotmc
2031 +
2032 +This module does some really basic monitoring for Spigot Minecraft servers.
2033 +
2034 +It provides two charts, one tracking server-side ticks-per-second in
2035 +1, 5 and 15 minute averages, and one tracking the number of currently
2036 +active users.
2037 +
2038 +This is not compatible with Spigot plugins which change the format of
2039 +the data returned by the `tps` or `list` console commands.
2040 +
2041 +### configuration
2042 +
2043 +```yaml
2044 +host: localhost
2045 +port: 25575
2046 +password: pass
2047 +```
2048 +
2049 +By default, a connection to port 25575 on the local system is attempted with an empty password.
2050 +
2051 +---
2052 +
2053 # springboot
2054
2055 This module will monitor one or more Java Spring-boot applications depending on configuration.
python.d/python_modules/third_party/mcrcon.py new
+90
@@ -0,0 +1,90 @@
1 +# Minecraft Remote Console module.
2 +#
3 +# Copyright (C) 2015 Barnaby Gale
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
7 +# deal in the Software without restriction, including without limitation the
8 +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 +# sell 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
13 +# all copies of the Software and its documentation and acknowledgment shall be
14 +# given in the documentation and software packages that this Software was
15 +# used.
16 +#
17 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 +# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21 +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 +
24 +import socket
25 +import select
26 +import struct
27 +import time
28 +
29 +
30 +class MCRconException(Exception):
31 + pass
32 +
33 +
34 +class MCRcon(object):
35 + socket = None
36 +
37 + def connect(self, host, port, password):
38 + if self.socket is not None:
39 + raise MCRconException("Already connected")
40 + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
41 + self.socket.connect((host, port))
42 + self.send(3, password)
43 +
44 + def disconnect(self):
45 + if self.socket is None:
46 + raise MCRconException("Already disconnected")
47 + self.socket.close()
48 + self.socket = None
49 +
50 + def read(self, length):
51 + data = b""
52 + while len(data) < length:
53 + data += self.socket.recv(length - len(data))
54 + return data
55 +
56 + def send(self, out_type, out_data):
57 + if self.socket is None:
58 + raise MCRconException("Must connect before sending data")
59 +
60 + # Send a request packet
61 + out_payload = struct.pack('<ii', 0, out_type) + out_data.encode('utf8') + b'\x00\x00'
62 + out_length = struct.pack('<i', len(out_payload))
63 + self.socket.send(out_length + out_payload)
64 +
65 + # Read response packets
66 + in_data = ""
67 + while True:
68 + # Read a packet
69 + in_length, = struct.unpack('<i', self.read(4))
70 + in_payload = self.read(in_length)
71 + in_id, in_type = struct.unpack('<ii', in_payload[:8])
72 + in_data_partial, in_padding = in_payload[8:-2], in_payload[-2:]
73 +
74 + # Sanity checks
75 + if in_padding != b'\x00\x00':
76 + raise MCRconException("Incorrect padding")
77 + if in_id == -1:
78 + raise MCRconException("Login failed")
79 +
80 + # Record the response
81 + in_data += in_data_partial.decode('utf8')
82 +
83 + # If there's nothing more to receive, return the response
84 + if len(select.select([self.socket], [], [], 0)[0]) == 0:
85 + return in_data
86 +
87 + def command(self, command):
88 + result = self.send(2, command)
89 + time.sleep(0.003) # MC-72390 workaround
90 + return result
python.d/spigotmc.chart.py new
+78
@@ -0,0 +1,78 @@
1 +# -*- coding: utf-8 -*-
2 +# Description: spigotmc netdata python.d module
3 +# Author: Austin S. Hemmelgarn (Ferroin)
4 +
5 +import socket
6 +
7 +from bases.FrameworkServices.SimpleService import SimpleService
8 +
9 +from third_party import mcrcon
10 +
11 +PRECISION = 100
12 +
13 +ORDER = ['tps', 'users']
14 +
15 +CHARTS = {
16 + 'tps': {
17 + 'options': [None, 'Spigot Ticks Per Second', 'ticks', 'spigotmc', 'spigotmc.tps', 'line'],
18 + 'lines': [
19 + ['tps1', '1 Minute Average', 'absolute', 1, PRECISION],
20 + ['tps5', '5 Minute Average', 'absolute', 1, PRECISION],
21 + ['tps15', '15 Minute Average', 'absolute', 1, PRECISION]
22 + ]
23 + },
24 + 'users': {
25 + 'options': [None, 'Minecraft Users', 'users', 'spigotmc', 'spigotmc.users', 'area'],
26 + 'lines': [
27 + ['users', 'Users', 'absolute', 1, 1]
28 + ]
29 + }
30 +}
31 +
32 +class Service(SimpleService):
33 + def __init__(self, configuration=None, name=None):
34 + SimpleService.__init__(self, configuration=configuration, name=name)
35 + self.order = ORDER
36 + self.definitions = CHARTS
37 + self.host = self.configuration.get('host', 'localhost')
38 + self.port = self.configuration.get('port', 25575)
39 + self.password = self.configuration.get('password', '')
40 + self.console = mcrcon.MCRcon()
41 +
42 + def check(self):
43 + '''Check plugin configuration validity.
44 +
45 + This really just makes sure we can connect and authenticate
46 + correctly.'''
47 + try:
48 + self.console.connect(self.host, self.port, self.password)
49 + except (mcrcon.MCRconException, socket.error):
50 + return False
51 + return True
52 +
53 + def _get_data(self):
54 + data = {}
55 + try:
56 + raw = self.console.command('tps')
57 + # The above command returns a string that looks like this:
58 + # '§6TPS from last 1m, 5m, 15m: §a19.99, §a19.99, §a19.99\n'
59 + # The values we care about are the three numbers after the :
60 + tmp = raw.split(':')[1].split(',')
61 + data['tps1'] = float(tmp[0].lstrip()[2:]) * PRECISION
62 + data['tps5'] = float(tmp[1].lstrip()[2:]) * PRECISION
63 + data['tps15'] = float(tmp[2].lstrip().rstrip()[2:]) * PRECISION
64 + except (mcrcon.MCRconException, socket.error):
65 + data['tps1'] = None
66 + data['tps5'] = None
67 + data['tps15'] = None
68 + self.error('Unable to fetch TPS values.')
69 + try:
70 + raw = self.console.command('list')
71 + # The above command returns a string that looks like this:
72 + # 'There are 0/20 players online:'
73 + # We care about the first number here.
74 + data['users'] = int(raw.split()[2].split('/')[0])
75 + except (mcrcon.MCRconException, socket.error):
76 + data['users'] = None
77 + self.error('Unable to fetch user counts.')
78 + return data
web/dashboard_info.js
+14
@@ -359,6 +359,12 @@ netdataDashboard.menu = {
359 title: 'ntpd',
360 icon: '<i class="fas fa-clock"></i>',
361 info: 'Provides statistics for the internal variables of the Network Time Protocol daemon <b><a href="http://www.ntp.org/">ntpd</a></b> and optional including the configured peers (if enabled in the module configuration). The module presents the performance metrics as shown by <b><a href="http://doc.ntp.org/current-stable/ntpq.html">ntpq</a></b> (the standard NTP query program) using NTP mode 6 UDP packets to communicate with the NTP server.'
362 + },
363 +
364 + 'spigotmc': {
365 + title: 'Spigot MC',
366 + icon: '<i class="fas fa-eye"></i>',
367 + info: 'Provides basic performance statistics for the <b><a href=""https://www.spigotmc.org/">Spigot Minecraft</a></b> server.'
368 }
369 };
370
@@ -2064,6 +2070,14 @@ netdataDashboard.context = {
2070
2071 'ntpd.peer_precision': {
2072 height: 0.2
2073 + },
2074 +
2075 + 'spigotmc.tps': {
2076 + info: 'The running 1, 5, and 15 minute average number of server ticks per second. An idealized server will show 20.0 for all values, but in practice this almost never happens. Typical servers should show approximately 19.98-20.0 here. Lower values indicate progressively more server-side lag (and thus that you need better hardware for your server or a lower user limit). For every 0.05 ticks below 20, redstone clocks will lag behind by approximately 0.25%. Values below approximately 19.50 may interfere with complex free-running redstone circuits and will noticeably slow down growth.'
2077 + },
2078 +
2079 + 'spigotmc.users': {
2080 + info: 'THe number of currently connect users on the monitored Spigot server.'
2081 }
2082
2083 // ------------------------------------------------------------------------