summaryrefslogtreecommitdiffstats
path: root/python.d/python_modules
diff options
context:
space:
mode:
authorAustin S. Hemmelgarn <ahferroin7@gmail.com>2018-04-27 14:21:11 -0400
committerAustin S. Hemmelgarn <ahferroin7@gmail.com>2018-04-27 14:21:11 -0400
commit4b0a7d8c496c074f7342343c31a822a01213c2f5 (patch)
tree5df8290fb426573b0b30a924d91a370bc46f812f /python.d/python_modules
parentb7d5bb91fb5fefe7fd2a7f0e003da33b9b0defc4 (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.py90
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