diff options
author | Austin S. Hemmelgarn <ahferroin7@gmail.com> | 2018-04-27 14:21:11 -0400 |
---|---|---|
committer | Austin S. Hemmelgarn <ahferroin7@gmail.com> | 2018-04-27 14:21:11 -0400 |
commit | 4b0a7d8c496c074f7342343c31a822a01213c2f5 (patch) | |
tree | 5df8290fb426573b0b30a924d91a370bc46f812f /python.d/python_modules | |
parent | b7d5bb91fb5fefe7fd2a7f0e003da33b9b0defc4 (diff) |
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).
Diffstat (limited to 'python.d/python_modules')
-rw-r--r-- | python.d/python_modules/third_party/mcrcon.py | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/python.d/python_modules/third_party/mcrcon.py b/python.d/python_modules/third_party/mcrcon.py new file mode 100644 index 0000000000..52f8d92440 --- /dev/null +++ b/python.d/python_modules/third_party/mcrcon.py @@ -0,0 +1,90 @@ +# Minecraft Remote Console module. +# +# Copyright (C) 2015 Barnaby Gale +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies of the Software and its documentation and acknowledgment shall be +# given in the documentation and software packages that this Software was +# used. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import socket +import select +import struct +import time + + +class MCRconException(Exception): + pass + + +class MCRcon(object): + socket = None + + def connect(self, host, port, password): + if self.socket is not None: + raise MCRconException("Already connected") + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect((host, port)) + self.send(3, password) + + def disconnect(self): + if self.socket is None: + raise MCRconException("Already disconnected") + self.socket.close() + self.socket = None + + def read(self, length): + data = b"" + while len(data) < length: + data += self.socket.recv(length - len(data)) + return data + + def send(self, out_type, out_data): + if self.socket is None: + raise MCRconException("Must connect before sending data") + + # Send a request packet + out_payload = struct.pack('<ii', 0, out_type) + out_data.encode('utf8') + b'\x00\x00' + out_length = struct.pack('<i', len(out_payload)) + self.socket.send(out_length + out_payload) + + # Read response packets + in_data = "" + while True: + # Read a packet + in_length, = struct.unpack('<i', self.read(4)) + in_payload = self.read(in_length) + in_id, in_type = struct.unpack('<ii', in_payload[:8]) + in_data_partial, in_padding = in_payload[8:-2], in_payload[-2:] + + # Sanity checks + if in_padding != b'\x00\x00': + raise MCRconException("Incorrect padding") + if in_id == -1: + raise MCRconException("Login failed") + + # Record the response + in_data += in_data_partial.decode('utf8') + + # If there's nothing more to receive, return the response + if len(select.select([self.socket], [], [], 0)[0]) == 0: + return in_data + + def command(self, command): + result = self.send(2, command) + time.sleep(0.003) # MC-72390 workaround + return result |